为啥 PIPE 文件的 readline() 这么慢?
Posted
技术标签:
【中文标题】为啥 PIPE 文件的 readline() 这么慢?【英文标题】:Why is readline() so slow for PIPE files?为什么 PIPE 文件的 readline() 这么慢? 【发布时间】:2014-07-11 20:10:35 【问题描述】:我正在尝试读取一个巨大的压缩 csv 文件并处理每一行。
我尝试了 2 种不同的实现方式:
碰巧通常推荐的实现比替代方案慢 100 倍。我错了还是Popen().stdout
的实施真的很糟糕? (似乎是逐字符读取文件)。
from time import time
from subprocess import Popen, PIPE
# We generate a csv file with 1M lines of 3D coordinates
from random import random
import os
N = 1000000
PATH = 'test'
GZIP_PATH = 'test.gz'
with open(PATH, 'w') as datafile:
for i in xrange(N):
datafile.write('0, 1, 2\n'.format(random(), random(), random()))
try:
os.remove(GZIP_PATH)
except:
pass
Popen(['gzip', PATH]).wait()
# We want to process the file line by line
# We start with a textbook implementation
def simple_generator(file):
line = file.readline()
while line:
yield line[:-1]
line = file.readline()
with Popen(['gunzip', '-c', GZIP_PATH], stdout=PIPE).stdout as datafile:
t = time()
i = 0
for line in simple_generator(datafile):
i+=1 # process the line
print time()-t
print i
# We start a lower level implementation
BLOCK_SIZE = 1<<16
def fast_generator(file):
rem = ''
block = file.read(BLOCK_SIZE)
while block:
lines = block.split('\n')
lines[0] = rem+lines[0]
for i in xrange(0,len(lines)-1):
yield lines[i]
rem = lines[-1]
block = file.read(BLOCK_SIZE)
with Popen(['gunzip', '-c', GZIP_PATH], stdout=PIPE).stdout as datafile:
t = time()
i = 0
for line in fast_generator(datafile):
i+=1 # process the line
print time()-t
print i
# Output:
#
# 34.0195429325
# 1000000
# 0.232397794724
# 1000000
#
# The second implementation is 100x faster!
【问题讨论】:
教科书实施?我从未见过在这种情况下推荐使用file.readline()
。通常的习惯用法是直接将文件用作迭代器,for line in file
,它的执行速度与您的低级实现基本相同。
你应该使用 python gzip 模块而不是 Popen:docs.python.org/3/library/gzip.html
@ngrislain 是的,它逐字节读取,因为默认行为是 unbuffered,如 Popen
所述。设置一个缓冲区大小,应该会更快。
@ChristianThieme: 否 gzip
模块不允许处理非常大的文件,因为它将整个文件加载到内存中。
@LukasGraf: for line in file
不适用于 python 2.5 中的 PIPE 文件bugs.python.org/issue3907
【参考方案1】:
正确的实现应该是用bufsize=-1
调用Popen
with Popen(['gunzip', '-c', GZIP_PATH], stdout=PIPE, bufsize=-1).stdout as datafile:
t = time()
i = 0
for line in simple_generator(datafile):
i+=1 # process the line
print time()-t
print i
我很惊讶默认行为是bufsize=0
。
【讨论】:
这确实解决了性能差距,但有趣的是,当我运行这些测试时,fast_generator
仍然比 simple_generator
快 30-40%。以上是关于为啥 PIPE 文件的 readline() 这么慢?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 fs.createReadStream ... pipe(res) 锁定读取文件?
为啥 BufferedReader read() 比 readLine() 慢得多?