python中更快、更好的gunzip(和一般文件输入/输出)

JHi*_*ant 3 python io optimization gzip gunzip

你如何以与底层库相当的速度使用 python gzip/gunzip 文件?

tl;dr - Use shutil.copyfileobj(f_in, f_out).
Run Code Online (Sandbox Code Playgroud)

我正在将 *.gz 文件解压缩为更大系列文件处理的一部分,并进行分析以尝试让 python 对内置脚本执行“关闭”。考虑到我正在处理的数据量,这很重要,而且理解起来似乎是一件很重要的事情。

在 ~500MB 上使用 'gunzip' bash 命令产生如下结果:

$time gunzip data.gz -k

real    0m24.805s
Run Code Online (Sandbox Code Playgroud)

一个简单的 python 实现看起来像:

with open('data','wb') as out:
    with gzip.open('data.gz','rb') as fin:
        s = fin.read()
        out.write(s)

real    2m11.468s
Run Code Online (Sandbox Code Playgroud)

不要将整个文件读入内存:

with open('data','wb') as out:
    with gzip.open('data.gz','rb') as fin:
        out.write(fin.read())

real    1m35.285s
Run Code Online (Sandbox Code Playgroud)

检查本地机器缓冲区大小:

>>> import io
>>> print io.DEFAULT_BUFFER_SIZE
8192
Run Code Online (Sandbox Code Playgroud)

使用缓冲:

with open('data','wb', 8192) as out:
    with gzip.open('data.gz','rb', 8192) as fin:
        out.write(fin.read())

real    1m19.965s
Run Code Online (Sandbox Code Playgroud)

使用尽可能多的缓冲:

with open('data','wb',1024*1024*1024) as out:
    with gzip.open('data.gz','rb', 1024*1024*1024) as fin:
        out.write(fin.read())

real    0m50.427s
Run Code Online (Sandbox Code Playgroud)

很明显,它是缓冲/IO 限制的。

我有一个中等复杂的版本,运行时间为 36 秒,但涉及预先分配的缓冲区和紧密的内部循环。我希望有一种“更好的方法”。

上面的代码合理且清晰,尽管仍然比 bash 脚本慢。但是如果有一个非常迂回或复杂的解决方案,它就不适合我的需求。我的主要警告是我想看到一个“pythonic”的答案。

当然,总有这样的解决方案:

subprocess.call(["gunzip","-k", "data.gz"])

real    0m24.332s
Run Code Online (Sandbox Code Playgroud)

但是就这个问题而言,是否有一种更快的方式来“pythonically”处理文件。

JHi*_*ant 5

我要发布我自己的答案。事实证明,您确实需要使用中间缓冲区;python 不能很好地为你处理这个问题。您确实需要调整该缓冲区的大小,并且“默认缓冲区大小”确实获得了最佳解决方案。就我而言,非常大的缓冲区 (1GB) 和小于默认值 (1KB) 的缓冲区较慢。

此外,我尝试了内置的 io.BufferedReader 和 io.BufferedWriter 类及其 readinto() 选项,发现这不是必需的。(不完全正确,因为 gzip 库是一个 BufferedReader 所以提供了这个。)

import gzip

buf = bytearray(8192)
with open('data', 'wb') as fout:
    with gzip.open('data.gz', 'rb') as fin:
        while fin.readinto(buf):
            fout.write(buf)

real    0m27.961s
Run Code Online (Sandbox Code Playgroud)

虽然我怀疑这是一个已知的 python 模式,但似乎有很多人对此感到困惑,所以我将把它放在这里,希望它可以帮助其他人。

@StefanPochmann 得到了正确答案。我希望他发布它,我会接受。解决办法是:

import gzip
import shutil
with open('data', 'wb') as fout:
    with gzip.open('data.gz', 'rb') as fin:
        shutil.copyfileobj(fin,fout)

real    0m26.126s
Run Code Online (Sandbox Code Playgroud)

  • 如 [`gzip` 示例](https://docs.python.org/3.6/library/gzip.html#examples-of-usage) 中建议的那样使用 `shutil.copyfileobj` 怎么样?(当然解压缩而不是压缩除外) (3认同)