Heim  >  Artikel  >  Backend-Entwicklung  >  Leistungsvergleichstest von PyPy und CPython

Leistungsvergleichstest von PyPy und CPython

高洛峰
高洛峰Original
2016-10-18 13:33:281418Durchsuche

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([&#39; {}="{}"&#39;.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&#39; {}="{}"&#39;.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&#39; {}="{}"&#39;.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
/ Zeit

PyPy 2169,90s

CPython 4494,69s

Ich 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
/ Zeit

PyPy 175,11s Benutzermodus 66,11s Systemmodus 64 % CPU

CPython 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

PyPy 14,79 s Benutzermodus 6,22 s Systemmodus 69 % CPU 30,322 Gesamt

CPython 44,20 s Benutzermodus 13,86 s Systemmodus 71 % CPU 1:20,91 Gesamt

Um ehrlich zu sein, erfordert diese Aufgabe auch den Aufbau einiger Tabu-Listen – um die Eingabe unnötiger Kategorien zu vermeiden. Aber darum geht es in diesem Artikel nicht.

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.

Im Vergleich zu CPython hat PyPy die Leistung in meinem einfachen Datenbankbetrieb um das Zwei- bis Dreifache verbessert. (Den SQL-Parser zähle ich hier nicht mit, das ist etwa das 8-fache)

Dank PyPy ist meine Arbeit angenehmer – ich habe Python effizient gemacht, ohne den Algorithmus neu zu schreiben, und PyPy verwendet kein CPython wie CPython. Meine CPU blieb hängen und ich konnte meinen Laptop eine Zeit lang nicht richtig nutzen (sehen Sie sich nur den Prozentsatz der CPU-Zeit an).

Bei den Aufgaben handelt es sich fast ausschließlich um Datenbankoperationen, und CPython verfügt über einige beschleunigte, chaotische C-Sprachmodule. PyPy verwendet diese nicht, aber die Ergebnisse sind schneller!

Alle meine Arbeiten erfordern viele Zyklen, daher freue ich mich sehr, PyPy zu verwenden.

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn