Comparaison de vitesse du traitement de texte intégral de Wiktionary avec F # et Python

J'ai écrit le processus de lecture du texte complet à partir du vidage Wiktionary en Python, mais je vais le porter sur F # et comparer la vitesse de traitement.

Ceci est une série d'articles.

  1. Explorer des méthodes de traitement efficaces pour Wiktionary
  2. Comparaison de la vitesse de traitement de Wiktionnaire entre F # et Python ← Cet article

Le script de cet article est publié dans le référentiel suivant.

Aperçu

Quand j'ai commencé à vider Wiktionary pour la première fois, je pensais utiliser F #, mais le .NET Framework ne pouvait pas gérer bzip2 par défaut, j'ai donc commencé à l'implémenter en Python. Une fois mis en parallèle, le processus s'est terminé en un peu plus d'une minute, j'ai donc estimé que Python était suffisant en termes de vitesse.

Pourtant, je me demandais à quelle vitesse F # serait, alors je vais essayer de compenser la bibliothèque manquante.

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

Nombre de lignes

Une bibliothèque externe est requise pour fonctionner avec bzip2 dans le .NET Framework.

Pour commencer, lisez toutes les lignes du vidage et comptez le nombre de lignes. Répertoriez le code Python et la durée correspondants.

Langue code Temps requis
Python python/research/countlines.py 3m34.911s
F# fsharp/research/countlines.fsx 2m40.270s
commander bzcat FILE.xml.bz2 | wc -l 2m32.203s

F # est la vitesse à l'approche de la commande.

#r "AR.Compression.BZip2.dll"
open System
open System.IO
open System.IO.Compression

let target = "enwiktionary-20200501-pages-articles-multistream.xml.bz2"
let mutable lines = 0
do
    use fs = new FileStream(target, FileMode.Open)
    use bs = new BZip2Stream(fs, CompressionMode.Decompress)
    use sr = new StreamReader(bs)
    while not (isNull (sr.ReadLine())) do
        lines <- lines + 1
Console.WriteLine("lines: {0:#,0}", lines)

Division de flux

Lisez le fichier bzip2 séparément pour chaque flux.

Masquez et lisez FileStream pour éviter la surcharge de passer par les octets. Utilisez le SubStream créé dans l'article suivant.

Utilisez les données de longueur de flux (streamlen.tsv) générées dans l'article précédent (https://qiita.com/7shi/items/e8091f6ac72491ad45a6).

Langue code Temps requis
Python python/research/countlines-BytesIO.py 3m37.827s
F# fsharp/research/countlines-split.fsx 5m23.122s

Apparemment, il y a une surcharge non négligeable au démarrage de la lecture du BZip2Stream, qui est assez lente. J'ai utilisé SubStream pour réduire encore un peu les frais généraux, mais je ne peux pas du tout rattraper.

#r "AR.Compression.BZip2.dll"
#load "StreamUtils.fsx"
open System
open System.IO
open System.IO.Compression
open StreamUtils

let target, slen =
    use sr = new StreamReader("streamlen.tsv")
    sr.ReadLine(),
    [|  while not <| sr.EndOfStream do
        yield sr.ReadLine() |> Convert.ToInt32 |]

let mutable lines = 0
do
    use fs = new FileStream(target, FileMode.Open)
    for length in slen do
        use ss = new SubStream(fs, length)
        use bs = new BZip2Stream(ss, CompressionMode.Decompress)
        use sr = new StreamReader(bs)
        while not (isNull (sr.ReadLine())) do
            lines <- lines + 1
Console.WriteLine("lines: {0:#,0}", lines)

Évitez les frais généraux

Une fois parallélisé en Python, je lis tous les 10 flux pour réduire la surcharge de la communication inter-processus, mais je prends la même action. Essayez également de lire tous les 100 flux à des fins de comparaison.

Langue code Temps requis Remarques
F# fsharp/research/countlines-split-10.fsx 2m50.913s Tous les 10 flux
F# fsharp/research/countlines-split-100.fsx 2m40.727s Tous les 100 flux

C'est beaucoup plus rapide. Étant donné que tous les 100 flux sont plus rapides, l'étape suivante consiste à fractionner tous les 100 flux.

let mutable lines = 0
do
    use fs = new FileStream(target, FileMode.Open)
    for lengths in Seq.chunkBySize 100 slen do
        use ss = new SubStream(fs, Array.sum lengths)
        use bs = new BZip2Stream(ss, CompressionMode.Decompress)
        use sr = new StreamReader(bs)
        while not (isNull (sr.ReadLine())) do
            lines <- lines + 1
Console.WriteLine("lines: {0:#,0}", lines)

Divisez par 100 éléments avec Seq.chunkBySize et additionnez avec ʻArray.sum`.

Conversion de chaîne

En Python, il était plus rapide de se convertir en une chaîne et d'utiliser StringIO. Essayez le même processus avec F #.

Langue code Temps requis Remarques
Python python/research/countlines-StringIO.py 3m18.568s Par flux
F# fsharp/research/countlines-split-string.fsx 7m50.915s Par flux
F# fsharp/research/countlines-split-string-10.fsx 3m55.453s Tous les 10 flux
F# fsharp/research/countlines-split-string-100.fsx 3m23.417s Tous les 100 flux

Cette méthode est rejetée car elle n'est pas très rapide en F #.

let mutable lines = 0
do
    use fs = new FileStream(target, FileMode.Open)
    for length in slen do
        let text =
            use ss = new SubStream(fs, length)
            use bs = new BZip2Stream(ss, CompressionMode.Decompress)
            use ms = new MemoryStream()
            bs.CopyTo(ms)
            Encoding.UTF8.GetString(ms.ToArray())
        use sr = new StringReader(text)
        while not (isNull (sr.ReadLine())) do
            lines <- lines + 1
Console.WriteLine("lines: {0:#,0}", lines)

Extraction de texte

Extrayez le contenu de la balise XML <text> et comptez le nombre de lignes.

partie commune


let mutable lines = 0
do
    use fs = new FileStream(target, FileMode.Open)
    fs.Seek(int64 slen.[0], SeekOrigin.Begin) |> ignore
    for lengths in Seq.chunkBySize 100 slen.[1 .. slen.Length - 2] do
        for _, text in getPages(fs, Array.sum lengths) do
            for _ in text do
                lines <- lines + 1
Console.WriteLine("lines: {0:#,0}", lines)

Essayez différentes méthodes. Remplace getPages par la méthode.

Traitement des chaînes

Analyse les balises avec le traitement des chaînes ligne par ligne.

Langue code Temps requis Remarques
Python python/research/countlines-text.py 4m06.555s startswith
F# fsharp/research/countlines-text-split-StartsWith.fsx 4m42.877s StartsWith
F# fsharp/research/countlines-text-split-slice.fsx 4m14.069s tranche
F# fsharp/research/countlines-text-split.fsx 4m05.507s Substring

La lenteur de StartsWith dans le .NET Framework semble être due à un traitement tel que la normalisation Unicode.

En utilisant Substring pour faire l'équivalent de StartsWith, c'est finalement à peu près aussi rapide que Python.

StartsWith


let getPages(stream, length) = seq {
    use ss = new SubStream(stream, length)
    use bs = new BZip2Stream(ss, CompressionMode.Decompress)
    use sr = new StreamReader(bs)
    let mutable ns, id = 0, 0
    while sr.Peek() <> -1 do
        let mutable line = sr.ReadLine().TrimStart()
        if line.StartsWith "<ns>" then
            ns <- Convert.ToInt32 line.[4 .. line.IndexOf('<', 4) - 1]
            id <- 0
        elif id = 0 && line.StartsWith "<id>" then
            id <- Convert.ToInt32 line.[4 .. line.IndexOf('<', 4) - 1]
        elif line.StartsWith "<text " then
            let p = line.IndexOf '>'
            if line.[p - 1] = '/' then () else
            if ns <> 0 then
                while not <| line.EndsWith "</text>" do
                line <- sr.ReadLine()
            else
                line <- line.[p + 1 ..]
                yield id, seq {
                    while not <| isNull line do
                    if line.EndsWith "</text>" then
                        if line.Length > 7 then yield line.[.. line.Length - 8]
                        line <- null
                    else
                        yield line
                        line <- sr.ReadLine() }}

Créez et remplacez une fonction qui remplace «StartsWith».

tranche


let inline startsWith (target:string) (value:string) =
    target.Length >= value.Length && target.[.. value.Length - 1] = value

Substring


let inline startsWith (target:string) (value:string) =
    target.Length >= value.Length && target.Substring(0, value.Length) = value

Analyseur XML

Comparez comment créer une arborescence avec un analyseur XML et comment analyser simplement sans créer d'arborescence.

L'élément racine est requis pour l'analyse XML. Comme nous l'avons déjà vu, F # est lent lors du passage de chaînes, nous combinons donc les flux pour le traitement. Utilisez le ConcatStream créé dans l'article suivant.

Il y a un arbre

Langue code Temps requis Remarques
Python python/research/countlines-text-xml.py 5m50.826s ElementTree.fromstring
F# fsharp/research/countlines-text-split-doc.fsx 6m21.588s XmlDocument

F # est plus lent que Python.

let getPages(stream, length) = seq {
    use ss = new SubStream(stream, length)
    use cs = new ConcatStream([
        new MemoryStream("<pages>"B)
        new BZip2Stream(ss, CompressionMode.Decompress)
        new MemoryStream("</pages>"B) ])
    use xr = XmlReader.Create(cs)
    let doc = XmlDocument()
    doc.Load(xr)
    for page in doc.ChildNodes.[0].ChildNodes do
        let ns = Convert.ToInt32(page.SelectSingleNode("ns").InnerText)
        if ns = 0 then
            let id = Convert.ToInt32(page.SelectSingleNode("id").InnerText)
            let text = page.SelectSingleNode("revision/text").InnerText
            use sr = new StringReader(text)
            yield id, seq {
                while sr.Peek() <> -1 do
                    yield sr.ReadLine() }}

Pas d'arbre

Veuillez vous référer aux articles suivants pour divers analyseurs Python.

F # utilise un XmlReader de type pull.

Langue code Temps requis Remarques
Python python/research/countlines-text-xmlparser.py 6m46.163s XMLParser
Python python/research/countlines-text-xmlpull.py 6m04.553s XMLPullParser
Python python/research/countlines-text-xmliter.py 6m29.298s ElementTree.iterparse
F# fsharp/research/countlines-text-split-reader.fsx 3m17.916s XmlReader
F# fsharp/research/countlines-text-reader.fsx 3m16.122s XmlReader(indivis)

Le .NET Framework XmlDocument est construit à l'aide de XmlReader. «XmlReader» est extrêmement plus rapide à utiliser seul. Dans les étapes qui suivent, nous n'utiliserons que la méthode XmlReader.

La situation devrait être la même pour Python, mais il semble que la création d'un arbre est beaucoup plus efficace et la création d'un arbre est plus rapide.

let getPages(stream, length) = seq {
    use ss = new SubStream(stream, length)
    use cs = new ConcatStream([
        new MemoryStream("<pages>"B)
        new BZip2Stream(ss, CompressionMode.Decompress)
        new MemoryStream("</pages>"B) ])
    use xr = XmlReader.Create(cs)
    let mutable ns, id = 0, 0
    while xr.Read() do
        if xr.NodeType = XmlNodeType.Element then
            match xr.Name with
            | "ns" ->
                if xr.Read() then ns <- Convert.ToInt32 xr.Value
                id <- 0
            | "id" ->
                if id = 0 && xr.Read() then id <- Convert.ToInt32 xr.Value
            | "text" ->
                if ns = 0 && not xr.IsEmptyElement && xr.Read() then
                    yield id, seq {
                        use sr = new StringReader(xr.Value)
                        while sr.Peek() <> -1 do
                            yield sr.ReadLine() }
            | _ -> () }

Tableau des langues

À partir du code jusqu'à présent, nous allons extraire les parties communes.

Créez une table dont les éléments contiennent des données dans quelle langue (ʻoutput1.tsv). Le nom de la langue est normalisé dans une autre table (ʻoutput2.tsv).

do
    use sw = new StreamWriter("output1.tsv")
    sw.NewLine <- "\n"
    for id, lid in results do
        fprintfn sw "%d\t%d" id lid
do
    use sw = new StreamWriter("output2.tsv")
    sw.NewLine <- "\n"
    for kv in langs do
        fprintfn sw "%d\t%s" kv.Value kv.Key

Puisqu'un article est séparé par la ligne == language name ==, il sera détecté et associé à id. Distinguer parce que === nom de l'élément === est utilisé dans les en-têtes inférieurs.

Comme nous l'avons déjà vu, «StartsWith» est lent. Comparez comment rechercher un caractère à la fois depuis le début d'une ligne avec une expression régulière.

L'analyse XML utilise le traitement des chaînes en Python et XmlReader en F #.

Langue code Temps requis Remarques
Python python/research/checklang.py 4m26.421s startswith
F# fsharp/research/checklang-StartsWith.fsx 3m43.965s StartsWith
Python python/research/checklang-ch.py 4m30.566s Caractère par personnage
F# fsharp/research/checklang.fsx 3m24.302s Caractère par personnage
Python python/research/checklang-re.py 5m9.869s Expressions régulières
F# fsharp/research/checklang-re.fsx 3m46.270s Expressions régulières

En F #, il est rapide de rechercher caractère par caractère, mais c'est lourd à mettre en œuvre et pas polyvalent. Les expressions régulières sont presque aussi rapides que «StartsWith». Compte tenu de la polyvalence, il semble sûr d'utiliser des expressions régulières.

StartsWith


            for line in text do
                if line.StartsWith "==" && not <| line.StartsWith "===" then
                    let lang = line.[2..].Trim()
                    let mutable e = lang.Length - 1
                    while e > 0 && lang.[e] = '=' do e <- e - 1
                    let lang = lang.[..e].Trim()

Caractère par personnage


            for line in text do
                if line.Length >= 3 && line.[0] = '=' && line.[1] = '=' && line.[2] <> '=' then

Expressions régulières


            for line in text do
                let m = r.Match line
                if m.Success then
                    let lang = m.Groups.[1].Value.Trim()

Parallélisation

F # utilise le multithreading et Python utilise le multiprocessus pour paralléliser.

Langue code Temps requis Remarques
Python python/research/checklang-parallel.py 1m16.566s startswith
F# fsharp/research/checklang-parallel.fsx 1m03.941s Caractère par personnage
Python python/research/checklang-parallel-re.py 1m19.372s Expressions régulières
F# fsharp/research/checklang-parallel-re.fsx 1m07.009s Expressions régulières

C'est une marge étroite. La croissance due à la parallélisation est plus importante en Python.

Caractère par personnage


let getlangs(pos, length) = async { return [
    use fs = new FileStream(target, FileMode.Open, FileAccess.Read)
    fs.Seek(pos, SeekOrigin.Begin) |> ignore
    for id, text in MediaWikiParse.getPages(fs, length) do
        for line in text do
            if line.Length >= 3 && line.[0] = '=' && line.[1] = '=' && line.[2] <> '=' then
                let lang = line.[2..].Trim()
                let mutable e = lang.Length - 1
                while e > 0 && lang.[e] = '=' do e <- e - 1
                yield id, lang.[..e].Trim() ]}

Expressions régulières


let getlangs(pos, length) = async { return [
    let r = Regex "^==([^=].*)=="
    use fs = new FileStream(target, FileMode.Open, FileAccess.Read)
    fs.Seek(pos, SeekOrigin.Begin) |> ignore
    for id, text in MediaWikiParse.getPages(fs, length) do
        for line in text do
            let m = r.Match line
            if m.Success then
                yield id, m.Groups.[1].Value.Trim() ]}

let results =
    sposlen.[1 .. sposlen.Length - 2]
    |> Seq.chunkBySize 100
    |> Seq.map Array.unzip
    |> Seq.map (fun (ps, ls) -> Array.min ps, Array.sum ls)
    |> Seq.map getlangs
    |> Async.Parallel
    |> Async.RunSynchronously
    |> List.concat
let langs = Dictionary<string, int>()
do
    use sw = new StreamWriter("output1.tsv")
    sw.NewLine <- "\n"
    for id, lang in results do
        let lid =
            if langs.ContainsKey lang then langs.[lang] else
            let lid = langs.Count + 1
            langs.[lang] <- lid
            lid
        fprintfn sw "%d\t%d" id lid
do
    use sw = new StreamWriter("output2.tsv")
    sw.NewLine <- "\n"
    for kv in langs do
        fprintfn sw "%d\t%s" kv.Value kv.Key

.NET Core et Mono

Mesuré avec .NET Core et Mono sur WSL1. Comparez avec les résultats .NET Framework sous Windows.

La correspondance avec l'abréviation est la suivante.

code Framework Core Mono Remarques
fsharp/research/checklang.fsx 3m24.302s 3m25.545s 4m22.330s
fsharp/research/checklang-re.fsx 3m46.270s 3m42.882s 4m51.236s Expressions régulières
fsharp/research/checklang-parallel.fsx 1m03.941s 0m59.014s 2m39.716s Parallèle
fsharp/research/checklang-parallel-re.fsx 1m07.009s 1m06.136s 3m28.074s Expression régulière parallèle

.NET Core semble être aussi rapide ou légèrement plus rapide que le .NET Framework.

.NET Core consiste essentiellement à créer un projet, mais c'était gênant car il y avait plusieurs fichiers exécutables, j'ai donc écrit la configuration par la méthode suivante et je l'ai fait.

Impressions

L'utilisation de la méthode la plus rapide a donné un F # légèrement plus rapide. C'était impressionnant que Python soit plus rapide même s'il était porté avec le même contenu de traitement.

Comme je l'ai essayé dans l'article suivant, il existe une énorme différence dans le traitement basé sur les caractères.

Recommended Posts

Comparaison de vitesse du traitement de texte intégral de Wiktionary avec F # et Python
Traitement asynchrone de Python ~ Comprenez parfaitement async et attendez ~
Comparaison de CoffeeScript avec la grammaire JavaScript, Python et Ruby
J'ai essayé de comparer la vitesse de traitement avec dplyr de R et pandas de Python
Comparaison de la vitesse de la perspective XML Python
Coexistence de Python2 et 3 avec CircleCI (1.0)
J'ai comparé la vitesse de Hash avec Topaz, Ruby et Python
Bases du traitement d'images binarisées par Python
Dessin avec Matrix-Reinventor of Python Image Processing-
Compréhension complète du threading Python et du multitraitement
Comparaison de la vitesse de transposition de la matrice par Python
Comparaison de vitesse de murmurhash3, md5 et sha1
J'ai remplacé le calcul numérique de Python par Rust et comparé la vitesse
J'ai 0 ans d'expérience en programmation et je défie le traitement des données avec python
Réhabilitation des compétences Python et PNL à partir de "100 Language Processing Knock 2015" (Chapitre 1)
J'ai mesuré la vitesse de la notation d'inclusion de liste, pendant et pendant avec python2.7.
Comparaison des performances du détecteur de visage avec Python + OpenCV
[Python3] Comparaison de vitesse, etc. sur la privation de numpy.ndarray
Comparez la vitesse d'ajout et de carte Python
Implémentation de l'arbre TRIE avec Python et LOUDS
Comparaison d'écriture R et Python (méthode de division mutuelle euclidienne)
Poursuite du développement multi-plateforme avec Electron et Python
Exemple de lecture et d'écriture de CSV avec Python
Comparaison de Python et Ruby (Environment / Grammar / Literal Edition)
Jouons avec Python Receive et enregistrez / affichez le texte du formulaire de saisie
Réhabilitation des compétences Python et PNL à partir de «Knock 100 Language Processing 2015» (chapitre 2 deuxième semestre)
Quoi utiliser pour les piles et les files d'attente Python (comparaison de vitesse de chaque structure de données)
Réhabilitation des compétences Python et PNL à partir de "100 Language Processing Knock 2015" (Chapitre 2 premier semestre)
Traitement pleine largeur et demi-largeur des données CSV en Python
Remarques sur le traitement d'images HDR et RAW avec Python
Vitesse de lecture Python netCDF4 et imbrication d'instructions for
Expérience de comparaison de la vitesse d'écriture de fichier entre python 2.7.9 et pypy 2.5.0
Téléchargez facilement et partiellement mp4 avec python et youtube-dl!
[Chapitre 5] Introduction à Python avec 100 coups de traitement du langage
Capture d'image / comparaison de la vitesse OpenCV avec et sans GPU
Visualisez la gamme d'insertions internes et externes avec python
Traitement de texte avec Python
Une comparaison rapide des bibliothèques de test Python et node.js
Défiez l'analyse des composants principaux des données textuelles avec Python
[Chapitre 3] Introduction à Python avec 100 coups de traitement du langage
Traitement d'image avec Python
[Chapitre 2] Introduction à Python avec 100 coups de traitement du langage
Tableau de comparaison des processus fréquemment utilisés de Python et Clojure
Traitement d'image avec Python (j'ai essayé de le binariser en art mosaïque 0 et 1)
Récapitulatif du traitement de la date en Python (datetime et dateutil)
Utilisez python installé par Pyenv avec Sublime REPL de Sublime Text 3
Divers traitements de Python
Gestion des versions de Node, Ruby et Python avec anyenv
[Chapitre 4] Introduction à Python avec 100 coups de traitement du langage
Recherche dans la base de données (vérification de la vitesse de traitement avec ou sans index)
Effectuer une analyse isocurrent des canaux en eau libre avec Python et matplotlib
[Python] Comment définir des noms de variables dynamiquement et comparer la vitesse
Débarrassez-vous des données sales avec Python et les expressions régulières
Détecter les objets d'une couleur et d'une taille spécifiques avec Python
Comparaison de l'utilisation des fonctions d'ordre supérieur dans Python 2 et 3
[Jouons avec Python] Traitement d'image en monochrome et points
La comparaison et l'optimisation des vitesses BASIC et C et assembleur jouent avec IchigoJam