Python文本文件处理速度问题
我在用Python处理一个比较大的文件时遇到了问题。我现在做的就是
f = gzip.open(pathToLog, 'r')
for line in f:
counter = counter + 1
if (counter % 1000000 == 0):
print counter
f.close
光是打开这个文件、读行和增加计数器,就花了大约10分25秒。
而在Perl中,处理同样的文件,做的事情还多一些(还涉及一些正则表达式),整个过程只花了大约1分17秒。
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的差不多呢?
补充说明 我试着直接解压文件,用open而不是gzip.open来处理,但这样总时间也只变成了大约4分14.972秒,还是太慢了。
我还把取余和打印的语句去掉,换成了pass,现在做的只是从一个文件移动到另一个文件。
5 个回答
我花了一些时间在这个问题上。希望这段代码能解决你的问题。它使用了 zlib 库,并且没有调用外部的东西。
gunzipchunks 方法会把压缩的 gzip 文件分成小块来读取,这样你就可以逐块处理(就像一个生成器)。
gunziplines 方法则会读取这些解压后的小块,每次给你一行内容,同样也可以逐行处理(又是一个生成器)。
最后,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 模块快很多。
如果你在网上搜索“为什么Python的gzip速度慢”,你会发现很多讨论,包括对Python 2.7和3.2的一些改进补丁。与此同时,你可以像在Perl中那样使用zcat,这个速度非常快。你的第一个函数处理一个5MB的压缩文件大约需要4.19秒,而第二个函数只需要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)
在Python(至少在版本<= 2.6.x)中,gzip格式的解析是通过Python实现的(基于zlib)。而且,它似乎在做一些奇怪的事情,具体来说,就是先将文件解压到内存的末尾,然后再丢弃超出请求读取大小的部分(接着在下一次读取时再做一次)。免责声明:我只看了gzip.read()
三分钟,所以我可能理解错了。不管我对gzip.read()的理解是否正确,gzip模块似乎并没有针对大数据量进行优化。可以尝试像在Perl中那样,启动一个外部进程(例如,查看subprocess
模块)。
编辑 实际上,我错过了原作者提到的普通文件I/O和压缩文件一样慢的说法(感谢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)
……在喝完茶,发现它还在运行后,我把它杀掉了,抱歉。然后我尝试了10万行,而不是1000万行:
>>> 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的读取时间绝对不可能和普通读取时间相同(这一点通过实验得到了证实)。Anonymouslemming,请再检查一下。