【问题标题】:Faster, better gunzip (and general file input/output) in pythonpython中更快,更好的gunzip(和通用文件输入/输出)
【发布时间】:2017-04-18 20:54:03
【问题描述】:

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

tl;dr - Use shutil.copyfileobj(f_in, f_out).

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

在 ~500MB 上使用 'gunzip' bash 命令,如下所示:

$time gunzip data.gz -k

real    0m24.805s

一个朴素的 Python 实现如下所示:

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

real    2m11.468s

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

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

real    1m35.285s

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

>>> import io
>>> print io.DEFAULT_BUFFER_SIZE
8192

使用缓冲:

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

real    1m19.965s

尽可能多地使用缓冲:

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

很明显它是缓冲/IO 绑定的。

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

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

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

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

real    0m24.332s

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

【问题讨论】:

  • 有时 python 并不总是答案,这有什么问题?
  • 您的示例确实没有任何意义:所有三个 python 示例 1) 只是复制并且根本不解压缩 2) 一次将文件全部读入内存 3) 没有方式受 io-buffering 限制。此外,gunzip 和 cpython 的 gzip 模块使用相同的底层库来完成所有工作
  • 感谢您收看这些内容。为必要的编辑道歉。我过早地点击提交。 1) 错过了我的工作代码中的 gzip 前缀。现在添加。 2/3) 通过缓冲它提高了 2 倍的速度。 4)是的,它确实使用了底层库,所以我试图理解它为什么这么慢,特别是考虑到子进程版本与底层库一样快。 4b) foo.gz 是从样本中剪切和粘贴的,现在已修复。 5.) 8219 是一个错字。应该是 8192,对应系统的缓冲区大小,加上小幅提速。
  • 这不可能是您正在运行的代码。 as in: 是无效的 Python 语法,因为 in 是关键字,因此不能用作变量名。请不要伪造成绩单。
  • “不要将整个文件读入内存” 版本确实会将整个文件读入内存。它与 "naive python implementation" 完全相同(除了创建该变量)。

标签: python optimization io gzip gunzip


【解决方案1】:

我将发布我自己的答案。事实证明,您确实需要使用中间缓冲区; 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

虽然我怀疑这是一个已知的 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

【讨论】:

  • 按照gzip examples 中的建议使用shutil.copyfileobj 怎么样? (当然解压缩而不是压缩除外)
  • 谢谢@StefanPochmann。是的,这是最好的解决方案。以 26 秒进入,这与原生解决方案足够接近。干杯。
  • 我不打算发布答案,部分原因是我无法为您安排时间。一旦可能,请随意接受你自己的。
猜你喜欢
  • 2017-09-03
  • 1970-01-01
  • 2014-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-04
  • 2011-06-10
  • 2011-07-23
相关资源
最近更新 更多