Heim > Artikel > Backend-Entwicklung > Leistungsvergleichstest von PyPy und CPython
Kürzlich habe ich einige Data-Mining-Aufgaben auf Wikipedia abgeschlossen. Es besteht aus diesen Teilen:
Wikipedia-Dump von enwiki-pages-articles.xml analysieren;
Kategorien und Seiten in MongoDB speichern;
Kategorienamen kopieren.
Ich habe die Leistung von CPython 2.7.3 und PyPy 2b an realen Aufgaben getestet. Die Bibliotheken, die ich verwende, sind:
redis 2.7.2
pymongo 2.4.2
Zusätzlich wird CPython von den folgenden Bibliotheken unterstützt:
hiredis
pymongo c-extensions
Der Test umfasst hauptsächlich das Parsen von Datenbanken, daher habe ich nicht erwartet, dass PyPy einen großen Nutzen bringt (ganz zu schweigen davon, dass der Datenbanktreiber von CPython in C geschrieben ist).
Nachfolgend beschreibe ich einige interessante Ergebnisse.
Wiki-Seitennamen extrahieren
Ich muss Wiki-Seitennamen für Seiten in allen Wikipedia-Kategorien erstellen Speichern Sie die neu zugewiesenen. Die einfachste Lösung sollte darin bestehen, enwiki-page.sql (das eine RDB-Tabelle definiert) in MySQL zu importieren, die Daten dann zu übertragen und neu zu verteilen. Aber ich wollte die MySQL-Anforderungen nicht erhöhen (Backbone haben! XD), also habe ich einen einfachen Parser für SQL-Einfügeanweisungen in reinem Python geschrieben, die Daten dann direkt aus enwiki-page.sql importiert und neu verteilt.
Diese Aufgabe hängt mehr von der CPU ab, daher bin ich hinsichtlich PyPy erneut optimistisch.
/ Zeit
PyPy 169,00 s Benutzermodus 8,52 s Systemmodus 90 % CPU
CPython 1287,13 s Benutzermodus 8,10 s Systemmodus 96 % CPU
Ich habe auch einen ähnlichen Join für page.id->category durchgeführt (der Speicher meines Laptops ist zu klein, um die Informationen für meine Tests aufzunehmen).
Kategorien aus Enwiki filtern Filtern Sie Kategorien in XML und speichern Sie sie im gleichen XML-Format wie die Kategorien. Deshalb habe ich mich für einen SAX-Parser entschieden, einen Wrapper-Parser, der sowohl in PyPy als auch in CPython funktioniert. Externes natives Kompilierungspaket (Kollegen in PyPy und CPython).
Der Code ist sehr einfach:
Element- und TextElement-Elemente enthalten Tag- und Textinformationen und stellen eine Methode zum Rendern bereit.
Das Folgende ist das Vergleichsergebnis von PyPy und CPython, das ich möchte.
class WikiCategoryHandler(handler.ContentHandler): """Class which detecs category pages and stores them separately """ ignored = set(('contributor', 'comment', 'meta')) def __init__(self, f_out): handler.ContentHandler.__init__(self) self.f_out = f_out self.curr_page = None self.curr_tag = '' self.curr_elem = Element('root', {}) self.root = self.curr_elem self.stack = Stack() self.stack.push(self.curr_elem) self.skip = 0 def startElement(self, name, attrs): if self.skip>0 or name in self.ignored: self.skip += 1 return self.curr_tag = name elem = Element(name, attrs) if name == 'page': elem.ns = -1 self.curr_page = elem else: # we don't want to keep old pages in memory self.curr_elem.append(elem) self.stack.push(elem) self.curr_elem = elem def endElement(self, name): if self.skip>0: self.skip -= 1 return if name == 'page': self.task() self.curr_page = None self.stack.pop() self.curr_elem = self.stack.top() self.curr_tag = self.curr_elem.tag def characters(self, content): if content.isspace(): return if self.skip == 0: self.curr_elem.append(TextElement(content)) if self.curr_tag == 'ns': self.curr_page.ns = int(content) def startDocument(self): self.f_out.write("<root>\n") def endDocument(self): self.f_out.write("<\root>\n") print("FINISH PROCESSING WIKIPEDIA") def task(self): if self.curr_page.ns == 14: self.f_out.write(self.curr_page.render()) class Element(object): def __init__(self, tag, attrs): self.tag = tag self.attrs = attrs self.childrens = [] self.append = self.childrens.append def __repr__(self): return "Element {}".format(self.tag) def render(self, margin=0): if not self.childrens: return u"{0}<{1}{2} />".format( " "*margin, self.tag, "".join([' {}="{}"'.format(k,v) for k,v in {}.iteritems()])) if isinstance(self.childrens[0], TextElement) and len(self.childrens)==1: return u"{0}<{1}{2}>{3}</{1}>".format( " "*margin, self.tag, "".join([u' {}="{}"'.format(k,v) for k,v in {}.iteritems()]), self.childrens[0].render()) return u"{0}<{1}{2}>\n{3}\n{0}</{1}>".format( " "*margin, self.tag, "".join([u' {}="{}"'.format(k,v) for k,v in {}.iteritems()]), "\n".join((c.render(margin+2) for c in self.childrens))) class TextElement(object): def __init__(self, content): self.content = content def __repr__(self): return "TextElement" def render(self, margin=0): return self.content/ ZeitPyPy 2169,90sCPython 4494,69sIch war sehr überrascht von den Ergebnissen von PyPy. Berechnung eines interessanten Satzes von Kategorien
Ich wollte einmal einen interessanten Satz von Kategorien berechnen – im Kontext einer meiner Anwendungen, abgeleitet von Berechnungskategorie Einige Kategorien zum Starten von Berechnungen. Dazu muss ich ein Klassendiagramm erstellen, das Klassen bereitstellt – ein Unterklassendiagramm.
Strukturklasse-Unterklassen-Beziehungsdiagramm
Diese Aufgabe verwendet MongoDB als Datenquelle und verteilt die Struktur neu. Der Algorithmus lautet:
Es tut mir leid, dass ich so einen Pseudocode schreibe, aber ich möchte, dass er kompakter aussieht.
Diese Aufgabe kopiert also nur Daten von einer Datenbank in eine andere. Die Ergebnisse hier werden erhalten, nachdem MongoDB aufgewärmt wurde (wenn die Daten nicht aufgewärmt werden, werden die Daten verzerrt – diese Python-Aufgabe verbraucht nur etwa 10 % der CPU). Das Timing ist wie folgt:
for each category.id in redis_categories (it holds *category.id -> category title mapping*) do: title = redis_categories.get(category.id) parent_categories = mongodb get categories for title for each parent_cat in parent categories do: redis_tree.sadd(parent_cat, title) # add to parent_cat set title/ ZeitPyPy 175,11s Benutzermodus 66,11s Systemmodus 64 % CPUCPython 457,92s Benutzermodus 72,86s Systemmodus 81 % CPU
Den redis_tree (umverteilten Baum) durchqueren
Wenn wir eine redis_tree-Datenbank haben, besteht das einzige verbleibende Problem darin Durchqueren Sie die Kategorie „Computer“. Laden Sie alle erreichbaren Knoten herunter. Um Schleifendurchquerungen zu vermeiden, müssen wir die besuchten Knoten aufzeichnen. Da ich die Datenbankleistung von Python testen wollte, habe ich dieses Problem durch eine Neuverteilung der Sammlungsspalten gelöst.
/ Zeit
Fazit
Die durchgeführten Tests sind nur eine Vorschau auf meine endgültige Arbeit. Es erfordert einen Wissensbestand, einen Wissensschatz, den ich durch das Extrahieren der entsprechenden Inhalte aus Wikipedia erhalte.