2010-04-22 5 views
9

Je rencontre un problème avec le traitement d'un fichier volumineux en Python. Tout ce que je fais estProblèmes de vitesse de traitement du fichier texte Python

f = gzip.open(pathToLog, 'r') 
for line in f: 
     counter = counter + 1 
     if (counter % 1000000 == 0): 
       print counter 
f.close 

Cela prend environ 10m25s juste pour ouvrir le fichier, lire les lignes et incrémenter ce compteur.

En Perl, traitant du même fichier et faisant un peu plus (quelque chose d'expression régulière), l'ensemble du processus prend environ 1m17s.

Perl code:

open(LOG, "/bin/zcat $logfile |") or die "Cannot read $logfile: $!\n"; 
while (<LOG>) { 
     if (m/.*\[svc-\w+\].*login result: Successful\.$/) { 
       $_ =~ s/some regex here/$1,$2,$3,$4/; 
       push @an_array, $_ 
     } 
} 
close LOG; 

Quelqu'un peut-il conseiller ce que je peux faire pour faire fonctionner la solution Python à une vitesse similaire à la solution Perl?

EDIT J'ai essayé juste décompressé le fichier et de traiter avec l'aide ouverte au lieu de gzip.open, mais qui ne change le temps total à environ 4m14.972s, ce qui est encore trop lent. J'ai aussi enlevé les instructions modulo et print et les ai remplacées par pass, donc tout ce qui est en train de se faire est de passer d'un fichier à un autre.

+7

Ce ne sont pas la même chose. La version PERL exécute deux processus simultanés. Vous devriez envisager d'utiliser une solution à deux processus en Python avant d'appeler Python lent. –

+3

En outre, vous imprimez quelque chose dans votre boucle dans la version Python, mais pas la version Perl. L'impression est relativement lente. –

+2

@Thomas mais tous les 1 million d'enregistrements ne devraient pas être le problème. Je pense que le si et modulo chaque enregistrement est plus d'un problème que l'impression elle-même. – extraneon

Répondre

9

En Python (au moins < = 2.6.x), l'analyse du format gzip est implémentée en Python (sur zlib). Plus, il semble faire des choses étranges, à savoir, décompresser à la fin du fichier à la mémoire, puis tout jeter au-delà de la taille de lecture demandée (puis le faire à nouveau pour la prochaine lecture). DISCLAIMER: Je viens de regarder gzip.read() pendant 3 minutes, donc je peux me tromper ici. Que ma compréhension de gzip.read() soit correcte ou non, le module gzip ne semble pas optimisé pour les gros volumes de données. Essayez de faire la même chose qu'en Perl, c'est-à-dire en lançant un processus externe (voir par exemple le module subprocess).


EDIT En fait, j'ai raté la remarque à propos de OP simple fichier I/O étant tout aussi lent que comprimé (grâce à ire_and_curses pour le signaler). Cela me striken improbable, donc je l'ai fait quelques mesures ...

from timeit import Timer 

def w(n): 
    L = "*"*80+"\n" 
    with open("ttt", "w") as f: 
     for i in xrange(n) : 
      f.write(L) 

def r(): 
    with open("ttt", "r") as f: 
     for n,line in enumerate(f) : 
      if n % 1000000 == 0 : 
       print n 

def g(): 
    f = gzip.open("ttt.gz", "r") 
    for n,line in enumerate(f) : 
     if n % 1000000 == 0 : 
     print n 

Maintenant, exécuter ...

>>> Timer("w(10000000)", "from __main__ import w").timeit(1) 
14.153118133544922 
>>> Timer("r()", "from __main__ import r").timeit(1) 
1.6482770442962646 
# here i switched to a terminal and made ttt.gz from ttt 
>>> Timer("g()", "from __main__ import g").timeit(1) 

... et après une pause de thé et de découvrir qu'il est toujours en cours Je l'ai tué, désolé. Alors j'ai essayé 100'000 lignes au lieu de 10'000'000:

>>> Timer("w(100000)", "from __main__ import w").timeit(1) 
0.05810999870300293 
>>> Timer("r()", "from __main__ import r").timeit(1) 
0.09662318229675293 
# here i switched to a terminal and made ttt.gz from ttt 
>>> Timer("g()", "from __main__ import g").timeit(1) 
11.939290046691895 

Module temps de gzip est O (file_size ** 2), donc avec nombre de lignes de l'ordre de millions, gzip temps de lecture ne peux pas être le même que le temps de lecture simple (comme nous le voyons confirmé par une expérience). Anonymouslemming, s'il vous plaît vérifier à nouveau.

+3

Ceci est intéressant, mais n'est-ce pas hors de propos? Que ce soit vrai ou pas, l'OP souligne que la boucle sur le fichier non compressé (et donc n'utilisant pas du tout le module 'gzip') en Python est également lente. –

+0

@ire_and_curses - merci de le signaler, je vais mettre à jour ma réponse. – atzz

+0

C'est vraiment bizarre à propos de 'gzip.open'. Est-ce un comportement attendu par conception? – Santa

0

Votre ordinateur a pris 10 minutes? Ce doit être votre matériel.J'ai écrit cette fonction pour écrire 5 millions de lignes:

def write(): 
    fout = open('log.txt', 'w') 
    for i in range(5000000): 
     fout.write(str(i/3.0) + "\n") 
    fout.close 

Puis je l'ai lu avec un programme beaucoup comme la vôtre:

def read(): 
    fin = open('log.txt', 'r') 
    counter = 0 
    for line in fin: 
     counter += 1 
     if counter % 1000000 == 0: 
      print counter 
    fin.close 

Il a fallu mon ordinateur environ 3 secondes pour lire les 5 millions de lignes.

+3

Mais combien de temps a-t-il fallu pour lire avec Perl sur votre matériel? C'est la performance relative, et non absolue, que le PO demandait. –

+0

Nous développons sur des machines virtuelles et déployons aux physiques. Comme @Cory dit, je ne suis pas inquiet de la performance autonome, je suis inquiet de la différence entre Python et Perl ici. – Anonymouslemming

+1

@Anonymouslemming Vous devriez prendre n'importe quel numéro de performance qui touche le disque dans une VM avec un grain de sel. Le disque IO est de loin la partie la plus faible des machines virtuelles. J'ai vu des machines virtuelles se bloquer pendant des heures sur un fsync. – mikelikespie

5

Si vous Google « pourquoi est lent gzip python », vous trouverez beaucoup de discussions de ce fait, y compris des correctifs pour des améliorations dans Python 2.7 et 3.2. En attendant, utilisez zcat comme vous l'avez fait en Perl qui est méchant rapidement. Votre (première) fonction me prend environ 4.19s avec un fichier compressé de 5MB, et la deuxième fonction prend 0.78s. Cependant, je ne sais pas ce qui se passe avec vos fichiers non compressés. Si je Décompressez les fichiers journaux (logs apache) et exécutez les deux fonctions sur eux avec un simple Python ouvert (fichier), et Popen (« chat »), Python est plus rapide (0.17s) que le chat (0.48s).

 
#!/usr/bin/python 

import gzip 
from subprocess import PIPE, Popen 
import sys 
import timeit 

#pathToLog = 'big.log.gz' # 50M compressed (*10 uncompressed) 
pathToLog = 'small.log.gz' # 5M "" 

def test_ori(): 
    counter = 0 
    f = gzip.open(pathToLog, 'r') 
    for line in f: 
     counter = counter + 1 
     if (counter % 100000 == 0): # 1000000 
      print counter, line 
    f.close 

def test_new(): 
    counter = 0 
    content = Popen(["zcat", pathToLog], stdout=PIPE).communicate()[0].split('\n') 
    for line in content: 
     counter = counter + 1 
     if (counter % 100000 == 0): # 1000000 
      print counter, line 

if '__main__' == __name__: 
    to = timeit.Timer('test_ori()', 'from __main__ import test_ori') 
    print "Original function time", to.timeit(1) 

    tn = timeit.Timer('test_new()', 'from __main__ import test_new') 
    print "New function time", tn.timeit(1) 
+0

Question rapide ... Quelle version de python est livré avec un sous-processus? Sur ma boîte je reçois Traceback (dernier appel en dernier): Fichier "./fileperformance.py", ligne 4, dans? du sous-processus import PIPE, Popen ImportError: Aucun module nommé sous-processus – Anonymouslemming

+0

Ah - c'est très inutile ... Navires avec 2.4 et RHEL4 a seulement 2.3.4 :( – Anonymouslemming

+0

Eh bien, vous pourriez nous juste backticks ou os.system() alors – Reagle

2

J'ai passé un moment là-dessus. Espérons que ce code fera l'affaire. Il utilise zlib et aucun appel externe. La méthode gunzipchunks lit le fichier gzip compressé en morceaux pouvant être itérés (générateur).

La méthode gunziplines lit ces blocs non compressés et vous fournit une ligne à la fois qui peut également être répétée (un autre générateur).

Enfin, la méthode gunziplinescounter vous offre ce que vous cherchez.

À la votre!

import zlib 

file_name = 'big.txt.gz' 
#file_name = 'mini.txt.gz' 

#for i in gunzipchunks(file_name): print i 
def gunzipchunks(file_name,chunk_size=4096): 
    inflator = zlib.decompressobj(16+zlib.MAX_WBITS) 
    f = open(file_name,'rb') 
    while True: 
     packet = f.read(chunk_size) 
     if not packet: break 
     to_do = inflator.unconsumed_tail + packet 
     while to_do: 
      decompressed = inflator.decompress(to_do, chunk_size) 
      if not decompressed: 
       to_do = None 
       break 
      yield decompressed 
      to_do = inflator.unconsumed_tail 
    leftovers = inflator.flush() 
    if leftovers: yield leftovers 
    f.close() 

#for i in gunziplines(file_name): print i 
def gunziplines(file_name,leftovers="",line_ending='\n'): 
    for chunk in gunzipchunks(file_name): 
     chunk = "".join([leftovers,chunk]) 
     while line_ending in chunk: 
      line, leftovers = chunk.split(line_ending,1) 
      yield line 
      chunk = leftovers 
    if leftovers: yield leftovers 

def gunziplinescounter(file_name): 
    for counter,line in enumerate(gunziplines(file_name)): 
     if (counter % 1000000 != 0): continue 
     print "%12s: %10d" % ("checkpoint", counter) 
    print "%12s: %10d" % ("final result", counter) 
    print "DEBUG: last line: [%s]" % (line) 

gunziplinescounter(file_name) 

Cela devrait fonctionner beaucoup plus rapidement que d'utiliser le module gzip intégré sur des fichiers extrêmement volumineux.

0

Essayez d'utiliser StringIO pour tamponner le signal de sortie du module gzip. Le code suivant pour lire un cornichon gzippé a réduit le temps d'exécution de mon code de plus de 90%.

Au lieu de ...

import cPickle 

# Use gzip to open/read the pickle. 
lPklFile = gzip.open("test.pkl", 'rb') 
lData = cPickle.load(lPklFile) 
lPklFile.close() 

utilisation ...

import cStringIO, cPickle 

# Use gzip to open the pickle. 
lPklFile = gzip.open("test.pkl", 'rb') 

# Copy the pickle into a cStringIO. 
lInternalFile = cStringIO.StringIO() 
lInternalFile.write(lPklFile.read()) 
lPklFile.close() 

# Set the seek position to the start of the StringIO, and read the 
# pickled data from it. 
lInternalFile.seek(0, os.SEEK_SET) 
lData = cPickle.load(lInternalFile) 
lInternalFile.close() 
Questions connexes