Проблемы со скоростью обработки текстовых файлов Python - PullRequest
9 голосов
/ 22 апреля 2010

У меня проблема с обработкой большого файла в Python. Все, что я делаю, это

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

Для открытия файла, чтения строк и увеличения этого счетчика требуется около 10 м 25 с.

В Perl, имея дело с тем же файлом и делая немного больше (некоторые вещи с регулярными выражениями), весь процесс занимает около 1m17s.

Код Perl:

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;

Кто-нибудь может посоветовать, что я могу сделать, чтобы решение Python работало с такой же скоростью, что и решение Perl?

EDIT Я попытался просто распаковать файл и разобраться с ним, используя open вместо gzip.open, но это только изменяет общее время до 4m14.972s, что все еще слишком медленно.

Я также удалил операторы modulo и print и заменил их на pass, так что все, что сейчас делается, - это перемещение из файла в файл.

Ответы [ 5 ]

9 голосов
/ 22 апреля 2010

В Python (по крайней мере <= 2.6.x) синтаксический анализ формата gzip реализован в Python (поверх zlib). Более того, он, кажется, делает некоторые странные вещи, а именно, распаковывает <strong>до конца файла в память, а затем отбрасывает все, что превышает запрошенный размер чтения (затем повторите это для следующего чтения). ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ : Я только что посмотрел на gzip.read() в течение 3 минут, поэтому я могу ошибаться здесь. Независимо от того, правильно ли я понимаю gzip.read (), модуль gzip не оптимизирован для больших объемов данных. Попробуйте сделать то же самое, что и в Perl, то есть запустить внешний процесс (например, см. Модуль subprocess).


EDIT На самом деле, я пропустил замечание ОП о том, что ввод / вывод в обычном файле такой же медленный, как сжатый (спасибо ire_and_curses за указание на это). Это показалось мне маловероятным, поэтому я сделал некоторые измерения ...

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

Теперь, запустив его ...

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

... и после перерыва на чай и обнаружения, что он все еще работает, я убил его, извините. Затем я попробовал 100'000 строк вместо 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

Время модуля gzip равно O (file_size ** 2), поэтому с числом строк порядка миллионов время чтения gzip не может быть таким же, как и время обычного чтения (как мы видим, подтверждается экспериментом). Анонимлемминг, пожалуйста, проверьте еще раз.

5 голосов
/ 23 апреля 2010

Если вы Google «почему Python GZIP медленный», вы найдете множество обсуждений этого, в том числе патчи для улучшений в Python 2.7 и 3.2. В то же время, используйте zcat, как вы это делали в Perl, что очень быстро. Ваша (первая) функция занимает у меня около 4,19 с сжатым файлом 5 МБ, а вторая функция занимает 0,78 с. Однако я не знаю, что происходит с вашими несжатыми файлами. Если я распаковываю файлы журналов (журналы apache) и запускаю на них две функции с простым открытым Python (файл) и Popen ('cat'), Python быстрее (0,17 с), чем cat (0,48 с).

#!/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)
2 голосов
/ 23 апреля 2010

Я потратил некоторое время на это.Надеюсь, этот код поможет.Он использует zlib и не использует внешние вызовы.

Метод gunzipchunks читает сжатый файл gzip порциями, которые можно перебирать (генератор).Метод читает эти несжатые блоки и предоставляет вам по одной строке за раз, которую также можно перебирать (другой генератор).

Наконец, метод gunziplinescounter дает вам то, что выищите.

Приветствия!

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)

Это должно работать намного быстрее, чем использование встроенного модуля gzip для очень больших файлов.

0 голосов
/ 17 сентября 2013

Попробуйте использовать StringIO для буферизации вывода из модуля gzip. Следующий код для чтения gzipped Pickle сократил время выполнения моего кода более чем на 90%.

Вместо ...

import cPickle

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

Использовать ...

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()
0 голосов
/ 23 апреля 2010

Твой компьютер занял 10 минут? Это должно быть ваше оборудование. Я написал эту функцию, чтобы написать 5 миллионов строк:

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

Затем я прочитал его с помощью программы, очень похожей на вашу:

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

Моему компьютеру понадобилось около 3 секунд, чтобы прочитать все 5 миллионов строк.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...