Geschwindigkeitsvergleich der Python-XML-Perspektive

Die ElementTree XML-API von Python bietet verschiedene Analysemethoden. Konzentrieren Sie sich auf Geschwindigkeit und vergleichen Sie.

Die für die Messung verwendete Umgebung ist wie folgt.

Überblick

Python verfügt über eine ElementTree XML-API für die Arbeit mit XML.

Es werden vier Hauptmethoden angeboten.

  1. fromstring
  2. XMLParser
  3. XMLPullParser
  4. iterparse

Diese werden gemäß Bedingungen wie Speichereffizienz und Blockiervermeidung ordnungsgemäß verwendet. Dieses Mal interessieren mich solche Bedingungen nicht, ich konzentriere mich nur auf die Verarbeitungszeit von riesigem XML.

Ziel

Wiktionary Targets Dump-Dateien der englischen Version. Die Dump-Daten werden komprimiert mit bzip2 bereitgestellt. Es wird verwendet, wenn es komprimiert wird, ohne es zu dekomprimieren. (Es wird ungefähr 6 GB sein, wenn es erweitert wird)

Die Dump-Datei hat die folgende Tag-Struktur.

<mediawiki>
    <siteinfo> ⋯ </siteinfo>
    <page> ⋯ </page>
    <page> ⋯ </page>
           ⋮
    <page> ⋯ </page>
</mediawiki>

Das Ganze ist so groß, dass es durch eine Methode namens Multi-Stream komprimiert wird, sodass nur ein Teil herausgenommen und verarbeitet werden kann.

siteinfopage × 100page × 100

Dieses Mal werden wir das Ganze verarbeiten, während wir die Streams einzeln abrufen.

Der zweite und nachfolgende Stream enthält 100 -Tags.

    <page> ⋯ </page>
    <page> ⋯ </page>
           ⋮
    <page> ⋯ </page>

Jedes <page> -Tag hat die folgende Struktur.

  <page>
    <title>Titel</title>
    <ns>Namensraum</ns>
    <id>ID</id>
    <revision>
      <id>Revision ID</id>
      <parentid>Revisions-ID vor dem Update</parentid>
      <timestamp>Datum und Uhrzeit aktualisieren</timestamp>
      <contributor>
        <username>Editor</username>
        <id>Editor-ID</id>
      </contributor>
      <comment>Kommentare zum Zeitpunkt der Bearbeitung</comment>
      <model>wikitext</model>
      <format>text/x-wiki</format>
      <text bytes="Länge" xml:space="preserve">Artikelinhalt</text>
      <sha1>Hashwert</sha1>
    </revision>
  </page>

Von diesen wird der Inhalt des Tags extrahiert und die Anzahl der Zeilen gezählt. Dies sind einige der Umfragen, die in den folgenden Artikeln durchgeführt wurden.

Die in diesem Artikel verwendeten Skripte sind in den folgenden Repositorys enthalten:

Implementieren Sie "getpage", um "" und "" von "" auf jede Weise abzurufen.

Der Teil, der die Anzahl der Zeilen mit getpage zählt, wird gemeinsam genutzt.

allgemeiner Teil


lines = 0
with open(target, "rb") as f:
    f.seek(slen[0])
    for length in slen[1:-1]:
        for id, text in getpages(f.read(length)):
            for line in text():
                lines += 1
print(f"lines: {lines:,}")

fromstring

Erstellen Sie einen Baum, indem Sie XML als Zeichenfolge übergeben. Führen Sie Operationen am Baum aus (suchen Sie nach Tags, rufen Sie sie ab usw.).

Code Verarbeitungszeit
python/research/countlines-text-xml.py 5m50.826s
def getpages(bz2data):
    xml = bz2.decompress(bz2data).decode("utf-8")
    pages = ET.fromstring(f"<pages>{xml}</pages>")
    for page in pages:
        if int(page.find("ns").text) == 0:
            id = int(page.find("id").text)
            with io.StringIO(page.find("revision/text").text) as t:
                def text():
                    while (line := t.readline()):
                        yield line
                yield id, text

Da XML ein Root-Tag erfordert, wird es vorübergehend in "" eingeschlossen. Wenn Sie dies entfernen, tritt ein Fehler auf.

<ns> repräsentiert den Seitentyp. Da 0 eine normale Seite ist, haben wir sie darauf eingegrenzt.

Es gibt verschiedene Arten von "". page.find (" id ") gilt nur für <id> direkt unter <page>. Da sich "" in "" befindet, geben Sie "page.find (" revision / text ")" an. Diese Spezifikationsmethode wird als XPath bezeichnet.

XMLParser

fromstring ist der Parser, der intern zum Erstellen des Baums verwendet wird. Die direkte Verwendung ist schnell, da Sie das Erstellen des Baums überspringen und ihn einfach analysieren können.

Es ist ein Parser vom Typ ** Push-Typ **, der fast der Methode SAX entspricht. Bereiten Sie eine Klasse vor, die Methoden zur Behandlung von Ereignissen wie Tag-Start und Tag-Ende enthält.

Code Verarbeitungszeit
python/research/countlines-text-xmlparser.py 6m46.163s
class XMLTarget:
    def __init__(self):
        self._ns   = 0
        self._id   = 0
        self._data = []
        self.pages = []

    def start(self, tag, attrib):
        self._data = []

    def data(self, data):
        self._data.append(data)

    def end(self, tag):
        if tag == "ns":
            self._ns = int(self._data[0])
            self._id = 0
        elif self._id == 0 and tag == "id":
            self._id = int(self._data[0])
        elif self._ns == 0 and tag == "text":
            text = []
            cur = ""
            for d in self._data:
                if d == "\n":
                    text.append(cur)
                    cur = ""
                else:
                    cur += d
            if cur: text.append(cur)
            self.pages.append((self._id, text))
        self._data = []

def getpages(bz2data):
    target = XMLTarget()
    parser = ET.XMLParser(target=target)
    parser.feed("<pages>")
    parser.feed(bz2.decompress(bz2data).decode("utf-8"))
    return target.pages

data ist der Inhalt des Tags. Da es getrennt durch lange Zeilen oder Zeilenumbrüche gesendet wird, wird es nach Zeilen gruppiert.

Klassenvariable

Die Dokumentation zeigt Beispielcode ähnlich dem folgenden.

>>> class MaxDepth:                     # The target object of the parser
...     maxDepth = 0
...     depth = 0
...     def start(self, tag, attrib):   # Called for each opening tag.
...         self.depth += 1
...         if self.depth > self.maxDepth:
...             self.maxDepth = self.depth
...     def end(self, tag):             # Called for each closing tag.
...         self.depth -= 1
...     def data(self, data):
...         pass            # We do not need to do anything with data.
...     def close(self):    # Called when all data has been parsed.
...         return self.maxDepth

Die direkt unter der Klasse definierten Variablen "maxDepth" und "depth" sind Klassenvariablen und allen Instanzen gemeinsam. In diesem Code werden Instanzvariablen erstellt und Klassenvariablen während Aktualisierungen wie "+ =" und "=" ausgeblendet. Daher wird der Wert der Klassenvariablen niemals mit "0" aktualisiert.

Die Geschichte ist jedoch anders, wenn Klassenvariablen Methoden wie "Anhängen" in Listen verwenden. Ich war süchtig danach und konnte den Code nicht schreiben, der wie erwartet funktionierte.

XMLPullParser

Es ist eine Art Parser namens ** Pull-Typ **. In Java heißt es StAX für SAX.

Im Gegensatz zum Push-Typ muss keine Klasse vorbereitet werden, und sie kann durch Schleifen und bedingte Verzweigungen verarbeitet werden, was den Code vereinfacht.

[Referenz] Vergleich zwischen XmlReader und SAX Reader | Microsoft Docs

Code Verarbeitungszeit
python/research/countlines-text-xmlpull.py 6m4.553s
def getpages(bz2data):
    xml = bz2.decompress(bz2data).decode("utf-8")
    parser = ET.XMLPullParser()
    parser.feed("<pages>")
    parser.feed(xml)
    ns, id = 0, 0
    for ev, el in parser.read_events():
        if el.tag == "ns":
            ns = int(el.text)
            id = 0
        elif id == 0 and el.tag == "id":
            id = int(el.text)
        elif ns == 0 and el.tag == "text":
            with io.StringIO(el.text) as t:
                def text():
                    while (line := t.readline()):
                        yield line
                yield id, text

Da es mehrere Arten von "" gibt, initialisieren und überprüfen Sie mit "id = 0", so dass nur die unmittelbar nach "" aufgenommen wird.

iterparse

Es ist ein Pull-Parser. Der Unterschied zu "XMLPullParser" wird wie folgt erklärt.

XMLPullParser ist so flexibel, dass es für Benutzer, die es einfach verwenden möchten, unpraktisch sein kann. Wenn Ihre Anwendung beim Lesen von XML-Daten nicht blockieren muss, sondern die Möglichkeit zum schrittweisen Analysieren haben soll, lesen Sie iterparse (). Dies ist nützlich, wenn Sie ein großes XML-Dokument lesen und nicht möchten, dass sich alles im Speicher befindet.

iterparse () gibt einen Iterator zurück und erweitert die Analyse jedes Mal, wenn Sie mit next () fortfahren. Die Struktur des verwendeten Codes entspricht fast der von "XMLPullParser".

Code Verarbeitungszeit
python/research/countlines-text-xmliter.py 6m29.298s
def getpages(bz2data):
    xml = bz2.decompress(bz2data).decode("utf-8")
    ns, id = 0, 0
    for ev, el in ET.iterparse(io.StringIO(f"<pages>{xml}</pages>")):
        if el.tag == "ns":
            ns = int(el.text)
            id = 0
        elif id == 0 and el.tag == "id":
            id = int(el.text)
        elif ns == 0 and el.tag == "text":
            with io.StringIO(el.text) as t:
                def text():
                    while (line := t.readline()):
                        yield line
                yield id, text

Zusammenfassung

Von allen Versuchen, die ich versucht habe, war der Baumbau "fromstring" der schnellste. Der Unterschied ist jedoch nicht groß. Wenn Sie also nicht mit Daten dieser Größenordnung (6 GB) arbeiten, ist dies kein großes Problem.

Methode Code Verarbeitungszeit
fromstring python/research/countlines-text-xml.py 5m50.826s
XMLParser python/research/countlines-text-xmlparser.py 6m46.163s
XMLPullParser python/research/countlines-text-xmlpull.py 6m04.553s
iterparse python/research/countlines-text-xmliter.py 6m29.298s

Als Referenz verwendet .NET Framework auch den Pull-Typ "XmlReader", um den Baum zu erstellen. Es ist schneller, "XmlReader" direkt zu verwenden, ohne einen Baum zu erstellen.

Methode Code Verarbeitungszeit Bemerkungen
XmlDocument fsharp/research/countlines-text-split-doc.fsx 6m21.588s Da ist ein Baum
XmlReader fsharp/research/countlines-text-split-reader.fsx 3m17.916s Kein Baum

Recommended Posts

Geschwindigkeitsvergleich der Python-XML-Perspektive
[Python3] Geschwindigkeitsvergleich usw. über den Entzug von numpy.ndarray
Geschwindigkeitsvergleich von Python, Java, C ++
Vergleich von 4 Arten von Python-Webframeworks
[Python] Parsen von zufällig generiertem XML [ElementTree]
Vergleich der Berechnungsgeschwindigkeit durch Implementierung von Python mpmath (willkürliche Genauigkeitsberechnung) (Hinweis)
Vergleich japanischer Konvertierungsmodule in Python3
Python-String-Vergleich / benutze 'Liste' und 'In' anstelle von '==' und 'oder'
Versuchen Sie den Geschwindigkeitsvergleich der BigQuery Storage API
Vergleich von Python Serverless Frameworks-Zappa mit Chalice
Vergleich der Matrixtranspositionsgeschwindigkeit durch Python
Geschwindigkeitsvergleich von murmurhash3, md5 und sha1
Erster Python 3 ~ Erster Vergleich ~
Grundlagen von Python ①
Kopie von Python
Einführung von Python
Leistungsvergleich des Gesichtsdetektors mit Python + OpenCV
Python Geschwindigkeitsvergleich Regex vs Startwith vs Str [: word_length]
Vergleichen Sie die Geschwindigkeit von Python Append und Map
Gründlicher Vergleich von drei morphologischen Python-Analysebibliotheken
Einfacher Vergleich von Python-Bibliotheken, die Excel betreiben
Geschwindigkeit: Element am Ende des Python-Arrays hinzufügen
R- und Python-Schreibvergleich (euklidische Methode der gegenseitigen Teilung)
Geschwindigkeitsbewertung der Ausgabe von CSV-Dateien in Python
Vergleich von Python und Ruby (Environment / Grammar / Literal Edition)
[Python] Operation der Aufzählung
Liste der Python-Module
Verwendung für Python-Stapel und -Warteschlangen (Geschwindigkeitsvergleich jeder Datenstruktur)
Vergleich der Ausführungszeit von Python SDP
Python ~ Grammatikgeschwindigkeit lernen ~
Geschwindigkeitsvergleich jeder Sprache nach der Monte-Carlo-Methode
Vereinheitlichung der Python-Umgebung
Kopie der Python-Einstellungen
Grundlagen der Python-Scraping-Grundlagen
Geschwindigkeit der Listeneinschlussnotation in Python
Python netCDF4 Lesegeschwindigkeit und Verschachtelung von for-Anweisungen
[Python] Verhalten von Argmax
Vergleich von LDA-Implementierungen
Experiment zum Vergleich der Schreibgeschwindigkeit von Dateien zwischen Python 2.7.9 und Pypy 2.5.0
Vergleich der Implementierung mehrerer exponentieller gleitender Durchschnitte (DEMA, TEMA) in Python
Vergleich von Online-Klassifikatoren
Verwendung von Python-Einheimischen ()
der Zen von Python
Installieren von Python 3.3 rc1
Ein schneller Vergleich der Testbibliotheken von Python und node.js.
Vergleich der Anpassungsprogramme
# 4 [Python] Grundlagen der Funktionen
Grundkenntnisse in Python
Vergleichstabelle häufig verwendeter Prozesse von Python und Clojure
Nüchterne Trivia von Python3
Zusammenfassung der Python-Argumente
Python Package Manager-Vergleich
Grundlagen von Python: Ausgabe
Installation von matplotlib (Python 3.3.2)
Anwendung von Python 3 vars
Verschiedene Verarbeitung von Python
Vergleich von CoffeeScript mit JavaScript-, Python- und Ruby-Grammatik
[Maya Python] Zerquetsche den Inhalt des Skripts 1 ~ Camera Speed Editor
Vergleich des in Python geschriebenen EMA-Codes (Exponential Moving Average)