Comparaison de la vitesse de la perspective XML Python

L'API XML ElementTree de Python fournit plusieurs méthodes d'analyse. Concentrez-vous sur la vitesse et comparez.

L'environnement utilisé pour la mesure est le suivant.

Aperçu

Python a une API XML ElementTree pour travailler avec XML.

Il existe quatre méthodes principales proposées.

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

Ceux-ci sont utilisés correctement selon des conditions telles que l'efficacité de la mémoire et l'évitement du blocage. Cette fois, je me fiche de ces conditions, je me concentre uniquement sur le temps de traitement d'un énorme XML.

Cible

Wiktionary cible les fichiers de vidage de la version anglaise. Les données de vidage sont fournies compressées avec bzip2. Il est utilisé tel qu'il est compressé sans le décompresser. (Ce sera environ 6 Go une fois étendu)

Le fichier de vidage a la structure de balise suivante.

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

Le tout est tellement énorme qu'il est compressé par une méthode appelée multi-stream afin que seule une partie puisse être retirée et traitée.

siteinfopage × 100page × 100

Cette fois, nous traiterons le tout en récupérant les flux un par un.

Le deuxième flux et les suivants contiennent 100 balises «».

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

Chaque balise <page> a la structure suivante.

  <page>
    <title>Titre</title>
    <ns>Espace de nom</ns>
    <id>ID</id>
    <revision>
      <id>Revision ID</id>
      <parentid>ID de révision avant la mise à jour</parentid>
      <timestamp>Mettre à jour la date et l'heure</timestamp>
      <contributor>
        <username>Éditeur</username>
        <id>ID de l'éditeur</id>
      </contributor>
      <comment>Commentaires au moment de l'édition</comment>
      <model>wikitext</model>
      <format>text/x-wiki</format>
      <text bytes="longueur" xml:space="preserve">Contenu de l'article</text>
      <sha1>Valeur de hachage</sha1>
    </revision>
  </page>

Parmi ceux-ci, le contenu de la balise «» est extrait et le nombre de lignes est compté. Voici quelques-unes des enquêtes menées dans les articles suivants.

Les scripts utilisés dans cet article sont contenus dans les référentiels suivants:

Implémentez getpage pour récupérer <id> ʻet de ʻ dans chaque sens.

La partie qui compte le nombre de lignes en utilisant getpage est partagée.

partie commune


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

Créez une arborescence en passant XML sous forme de chaîne. Effectuer des opérations sur l'arborescence (rechercher des balises, les récupérer, etc.).

Code Temps de traitement
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

Puisque XML nécessite une balise racine, il est temporairement inclus dans ` ''. Si vous supprimez ceci, une erreur se produira.

«» représente le type de page. Puisque «0» est une page normale, nous l'avons réduit à cela.

Il existe plusieurs types de «». page.find (" id ") s'applique uniquement à <id> directement sous <page>. Puisque <text> est à l'intérieur de <revision>, spécifiez page.find (" revision / text "). Cette méthode de spécification est appelée XPath.

XMLParser

fromstring est l'analyseur utilisé en interne pour construire l'arborescence. L'utiliser directement est rapide car vous pouvez ignorer la construction de l'arborescence et simplement l'analyser.

C'est un analyseur du type appelé ** type push **, qui est presque le même que la méthode appelée SAX. Préparez une classe qui a des méthodes pour gérer les événements tels que le début et la fin de la balise.

Code Temps de traitement
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 est le contenu de la balise. Comme il est envoyé séparé par de longues lignes ou des sauts de ligne, il est groupé par ligne.

Variable de classe

La documentation présente un exemple de code similaire au suivant.

>>> 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

Les variables maxDepth et depth définies directement sous la classe sont des variables de classe et sont communes à toutes les instances. Dans ce code, les variables d'instance sont créées et les variables de classe sont masquées lors des mises à jour telles que «+ =» et «=». Par conséquent, la valeur de la variable de classe n'est jamais mise à jour avec «0».

Mais l'histoire est différente lorsque les variables de classe utilisent des méthodes telles que «ajouter» dans les listes. J'étais accro à cela et je ne pouvais pas écrire le code qui fonctionnait comme prévu.

XMLPullParser

C'est un type d'analyseur appelé ** type pull **. En Java, il s'appelle StAX pour SAX.

Contrairement au type push, il n'est pas nécessaire de préparer une classe, et elle peut être traitée par des boucles et des branchements conditionnels, ce qui simplifie le code.

[Référence] Comparaison entre XmlReader et SAX Reader | Microsoft Docs

Code Temps de traitement
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

Puisqu'il existe plusieurs types de <id>, initialisez et vérifiez avec ʻid = 0pour que seul celui qui se trouve immédiatement après` soit sélectionné.

iterparse

C'est un analyseur de type pull. La différence avec «XMLPullParser» est expliquée comme suit.

XMLPullParser est si flexible qu'il peut être gênant pour les utilisateurs qui veulent simplement l'utiliser. Si votre application n'empêche pas le blocage lors de la lecture de données XML, mais souhaite pouvoir analyser de manière incrémentielle, consultez ʻiterparse () `. Ceci est utile si vous lisez un grand document XML et que vous ne voulez pas qu'il soit entièrement en mémoire.

ʻIterparse () retourne un itérateur et avance l'analyse à chaque fois que vous continuez avec next () . La structure du code utilisé est presque la même que celle de XMLPullParser`.

Code Temps de traitement
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

Résumé

De tous les essais que j'ai essayés, la construction d'arbres fromstring était la plus rapide. Cependant, la différence n'est pas grande, donc à moins que vous n'ayez affaire à des données de cette échelle (6 Go), ce ne sera pas un gros problème.

méthode code temps de traitement
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

Pour référence, le .NET Framework utilise également le type d'extraction XmlReader pour créer l'arborescence. Il est plus rapide d'utiliser directement XmlReader sans créer d'arborescence.

méthode code temps de traitement Remarques
XmlDocument fsharp/research/countlines-text-split-doc.fsx 6m21.588s Il y a un arbre
XmlReader fsharp/research/countlines-text-split-reader.fsx 3m17.916s Pas d'arbre

Recommended Posts

Comparaison de la vitesse de la perspective XML Python
[Python3] Comparaison de vitesse, etc. sur la privation de numpy.ndarray
Comparaison de vitesse de Python, Java, C ++
Comparaison de 4 types de frameworks Web Python
[Python] Analyse du XML généré aléatoirement [ElementTree]
Comparaison de la vitesse de calcul en implémentant python mpmath (calcul de précision arbitraire) (Note)
Comparaison des modules de conversion japonais en Python3
comparaison de chaînes python / utiliser 'list' et 'in' au lieu de '==' et 'ou'
Essayez la comparaison de vitesse de l'API BigQuery Storage
Comparaison des frameworks sans serveur Python-Zappa vs Chalice
Comparaison de la vitesse de transposition de la matrice par Python
Comparaison de vitesse de murmurhash3, md5 et sha1
Premier Python 3 ~ Première comparaison ~
Bases de python ①
Copie de python
Introduction de Python
Comparaison des performances du détecteur de visage avec Python + OpenCV
Comparaison de vitesse Python regex vs startswith vs str [: word_length]
Comparez la vitesse d'ajout et de carte Python
Comparaison approfondie de trois bibliothèques d'analyse morphologique Python
Comparaison simple des bibliothèques Python qui exploitent Excel
Vitesse: ajouter un élément à la fin du tableau Python
Comparaison d'écriture R et Python (méthode de division mutuelle euclidienne)
Évaluation de la vitesse de sortie du fichier CSV en Python
Comparaison de Python et Ruby (Environment / Grammar / Literal Edition)
[Python] Opération d'énumération
Liste des modules python
Quoi utiliser pour les piles et les files d'attente Python (comparaison de vitesse de chaque structure de données)
Comparaison du temps d'exécution de Python SDP
Python ~ Apprentissage rapide de la grammaire ~
Comparaison de vitesse de chaque langue par la méthode de Monte Carlo
Unification de l'environnement Python
Copie des préférences python
Principes de base du grattage Python
Vitesse de notation d'inclusion de liste en Python
Vitesse de lecture Python netCDF4 et imbrication d'instructions for
[python] comportement d'argmax
Comparaison des implémentations LDA
Expérience de comparaison de la vitesse d'écriture de fichier entre python 2.7.9 et pypy 2.5.0
Comparaison de l'implémentation de plusieurs moyennes mobiles exponentielles (DEMA, TEMA) en Python
Comparaison des classificateurs en ligne
Utilisation des locaux Python ()
le zen de Python
Installation de Python 3.3 rc1
Une comparaison rapide des bibliothèques de test Python et node.js
Comparaison des programmes d'adaptation
# 4 [python] Bases des fonctions
Connaissance de base de Python
Tableau de comparaison des processus fréquemment utilisés de Python et Clojure
Anecdotes sobres de python3
Résumé des arguments Python
Comparaison du gestionnaire de packages Python
Bases de python: sortie
Installation de matplotlib (Python 3.3.2)
Application de Python 3 vars
Divers traitements de Python
Comparaison de CoffeeScript avec la grammaire JavaScript, Python et Ruby
[Maya Python] Écraser le contenu du script 1 ~ Camera Speed Editor
Comparaison du code de moyenne mobile exponentielle (EMA) écrit en Python