web-dev-qa-db-fra.com

Traitement de texte - Python vs performances Perl

Voici mon script Perl et Python pour effectuer un traitement de texte simple à partir d'environ 21 fichiers journaux, chacun d'environ 300 Ko à 1 Mo (maximum) x 5 fois répétés (total de 125 fichiers, en raison de log répété 5 fois).

Code Python (code modifié pour utiliser re compilé et utiliser re.I)

#!/usr/bin/python

import re
import fileinput

exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists', re.I)
location_re = re.compile(r'^AwbLocation (.*?) insert into', re.I)

for line in fileinput.input():
    fn = fileinput.filename()
    currline = line.rstrip()

    mprev = exists_re.search(currline)

    if(mprev):
        xlogtime = mprev.group(1)

    mcurr = location_re.search(currline)

    if(mcurr):
        print fn, xlogtime, mcurr.group(1)

Code Perl

#!/usr/bin/Perl

while (<>) {
    chomp;

    if (m/^(.*?) INFO.*Such a record already exists/i) {
        $xlogtime = $1;
    }

    if (m/^AwbLocation (.*?) insert into/i) {
        print "$ARGV $xlogtime $1\n";
    }
}

Et, sur mon PC, les deux codes génèrent exactement le même fichier de résultat de 10 790 lignes. Et, voici le timing effectué sur les implémentations Perl et Python de Cygwin.

User@UserHP /cygdrive/d/tmp/Clipboard
# time /tmp/scripts/python/afs/process_file.py *log* *log* *log* *log* *log* >
summarypy.log

real    0m8.185s
user    0m8.018s
sys     0m0.092s

User@UserHP /cygdrive/d/tmp/Clipboard
# time /tmp/scripts/python/afs/process_file.pl *log* *log* *log* *log* *log* >
summarypl.log

real    0m1.481s
user    0m1.294s
sys     0m0.124s

À l'origine, cela prenait 10,2 secondes en utilisant Python et seulement 1,9 seconde en utilisant Perl pour ce traitement de texte simple.

(UPDATE) mais, après la version compilée re de Python, cela prend maintenant 8,2 secondes en Python et 1,5 secondes en Perl. Perl est toujours beaucoup plus rapide.

Existe-t-il un moyen d'améliorer la vitesse de Python du tout OR, il est évident que Perl sera le plus rapide pour un traitement de texte simple.

Soit dit en passant, ce n'était pas le seul test que j'ai fait pour un simple traitement de texte ... Et, chaque façon différente dont je fais le code source, toujours Perl gagne par une grande marge. Et, pas une seule fois Python n'a mieux performé pour les simples correspondances et impressions de m/regex/.

Veuillez ne pas suggérer d'utiliser C, C++, Assembly, d'autres versions de Python, etc.

Je recherche une solution utilisant Standard Python avec ses modules intégrés par rapport à Standard Perl (sans même utiliser les modules). Garçon, je souhaite utiliser Python pour toutes mes tâches en raison de sa lisibilité, mais pour abandonner la vitesse, je ne pense pas.

Donc, veuillez suggérer comment le code peut être amélioré pour obtenir des résultats comparables avec Perl.

MISE À JOUR: 2012-10-18

Comme d'autres utilisateurs l'ont suggéré, Perl a sa place et Python a sa place.

Donc, pour cette question, on peut conclure en toute sécurité que pour une simple expression rationnelle, correspondre à chaque ligne pour des centaines ou des milliers de fichiers texte et écrire les résultats dans un fichier (ou imprimer sur écran), Perl sera toujours, GAGNEZ toujours en performance pour ce travail. C'est aussi simple que ça.

Veuillez noter que lorsque je dis que Perl gagne en performances ... seuls Perl standard et Python sont comparés ... ne recourant pas à certains modules obscurs (obscurs pour un utilisateur normal comme moi) et n'appelant pas non plus C, C++, Bibliothèques d'assemblys de Python ou Perl. Nous n'avons pas le temps d'apprendre toutes ces étapes supplémentaires et l'installation pour un travail de correspondance de texte simple.

Ainsi, Perl est idéal pour le traitement de texte et l'expression régulière.

Python a sa place pour basculer dans d'autres endroits.

Mise à jour 2013-05-29: Un excellent article qui fait une comparaison similaire est ici . Perl gagne à nouveau pour la simple correspondance de texte ... Et pour plus de détails, lisez l'article.

66
ihightower

C'est exactement le genre de choses que Perl a été conçu pour faire, donc cela ne me surprend pas que ce soit plus rapide.

Une optimisation facile dans votre code Python serait de précompiler ces expressions régulières, afin qu'elles ne soient pas recompilées à chaque fois.

exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists')
location_re = re.compile(r'^AwbLocation (.*?) insert into')

Et puis dans votre boucle:

mprev = exists_re.search(currline)

et

mcurr = location_re.search(currline)

En soi, cela n'amènera pas par magie votre script Python en ligne avec votre script Perl, mais appeler à plusieurs reprises re dans une boucle sans compiler d'abord est une mauvaise pratique en Python.

18
Josh Wright

Hypothèse: Perl passe moins de temps à revenir en arrière dans les lignes qui ne correspondent pas en raison des optimisations qu'il a Python ne le fait pas.

Qu'obtenez-vous en remplaçant

^(.*?) INFO.*Such a record already exists

avec

^((?:(?! INFO).)*?) INFO.*Such a record already 

ou

^(?>(.*?) INFO).*Such a record already exists
14
ikegami

Les appels de fonction sont un peu chers en termes de temps en Python. Et pourtant, vous avez un appel de fonction invariant de boucle pour obtenir le nom de fichier à l'intérieur de la boucle:

fn = fileinput.filename()

Déplacez cette ligne au-dessus de la boucle for et vous devriez voir une amélioration de votre timing Python. Probablement pas assez pour battre Perl cependant.

4
Don O'Donnell

Je m'attends à ce que Perl soit plus rapide. Par simple curiosité, pouvez-vous essayer ce qui suit?

#!/usr/bin/python

import re
import glob
import sys
import os

exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists', re.I)
location_re = re.compile(r'^AwbLocation (.*?) insert into', re.I)

for mask in sys.argv[1:]:
    for fname in glob.glob(mask):
        if os.path.isfile(fname):
            f = open(fname)
            for line in f:
                mex = exists_re.search(line)
                if mex:
                    xlogtime = mex.group(1)

                mloc = location_re.search(line)
                if mloc:
                    print fname, xlogtime, mloc.group(1)
            f.close()

Mise à jour en réaction à "c'est trop complexe" .

Bien sûr, cela semble plus complexe que la version Perl. Le Perl a été construit autour des expressions régulières. De cette façon, vous pouvez difficilement trouver un langage interprété plus rapide dans les expressions régulières. La syntaxe Perl ...

while (<>) {
    ...
}

... cache également beaucoup de choses qui doivent être faites d'une manière ou d'une autre dans un langage plus général. D'un autre côté, il est assez facile de rendre le code Python plus lisible si vous retirez la partie illisible:

#!/usr/bin/python

import re
import glob
import sys
import os

def input_files():
    '''The generator loops through the files defined by masks from cmd.'''
    for mask in sys.argv[1:]:
        for fname in glob.glob(mask):
            if os.path.isfile(fname):
                yield fname


exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists', re.I)
location_re = re.compile(r'^AwbLocation (.*?) insert into', re.I)

for fname in input_files():
    with open(fname) as f:        # Now the f.close() is done automatically
        for line in f:
            mex = exists_re.search(line)
            if mex:
                xlogtime = mex.group(1)

            mloc = location_re.search(line)
            if mloc:
                print fname, xlogtime, mloc.group(1)

Ici, la def input_files() pourrait être placée ailleurs (par exemple dans un autre module), ou elle peut être réutilisée. Il est possible d'imiter même la while (<>) {...} de Perl facilement, même si ce n'est pas de la même manière syntaxiquement:

#!/usr/bin/python

import re
import glob
import sys
import os

def input_lines():
    '''The generator loops through the lines of the files defined by masks from cmd.'''
    for mask in sys.argv[1:]:
        for fname in glob.glob(mask):
            if os.path.isfile(fname):
                with open(fname) as f: # now the f.close() is done automatically
                    for line in f:
                        yield fname, line

exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists', re.I)
location_re = re.compile(r'^AwbLocation (.*?) insert into', re.I)

for fname, line in input_lines():
    mex = exists_re.search(line)
    if mex:
        xlogtime = mex.group(1)

    mloc = location_re.search(line)
    if mloc:
        print fname, xlogtime, mloc.group(1)

Le dernier for peut alors sembler aussi simple (en principe) que le while (<>) {...} de Perl. De telles améliorations de lisibilité sont plus difficiles en Perl.

Quoi qu'il en soit, cela ne rendra pas le programme Python plus rapide. Perl sera encore plus rapide ici. Perl est un fichier/text cruncher. Mais - à mon avis - Python est un meilleur langage de programmation à des fins plus générales.

1
pepr

En général, tous les benchmarks artificiels sont mauvais. Cependant, toutes choses étant égales par ailleurs (approche algorithmique), vous pouvez apporter des améliorations sur une base relative. Cependant, il convient de noter que je n'utilise pas Perl, donc je ne peux pas argumenter en sa faveur. Cela étant dit, avec Python vous pouvez essayer d'utiliser Pyrex ou Cython pour améliorer les performances. Ou, si vous êtes aventureux, vous pouvez essayer convertir le Python en C++ via ShedSkin (qui fonctionne pour la plupart du langage de base, et certains - mais pas tous, des modules de base).

Néanmoins, vous pouvez suivre certains des conseils affichés ici:

http://wiki.python.org/moin/PythonSpeed/PerformanceTips

1
jrd1