为啥 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) 锁定读取文件?

为啥 Linux 会重用 pipe() 分配的文件描述符

为啥 BufferedReader read() 比 readLine() 慢得多?

nodejs的readline怎么支持字符串逐行读取

在 Python 中对 subprocess.PIPE 进行非阻塞读取

在 Python 中对 subprocess.PIPE 进行非阻塞读取