使用管道在 Perl 中将管道文件输出到 gzip 的 Python 等效项

Posted

技术标签:

【中文标题】使用管道在 Perl 中将管道文件输出到 gzip 的 Python 等效项【英文标题】:Python equivalent of piping file output to gzip in Perl using a pipe 【发布时间】:2011-11-28 21:54:26 【问题描述】:

我需要弄清楚如何在Python中将文件输出写入压缩文件,类似于下面的两行:

open ZIPPED, "| gzip -c > zipped.gz";
print ZIPPED "Hello world\n";

在 Perl 中,这使用 Unix gzip 将您打印到 ZIPPED 文件句柄的任何内容压缩到文件“zipped.gz”。

我知道如何在 Python 中使用“import gzip”来执行此操作,如下所示:

import gzip
zipped = gzip.open("zipped.gz", 'wb')
zipped.write("Hello world\n")

但是,这非常慢。根据分析器的说法,使用该方法占用了我 90% 的运行时间,因为我正在将 200GB 的未压缩数据写入各种输出文件。我知道文件系统可能是这里问题的一部分,但我想通过使用 Unix/Linux 压缩来排除它。这部分是因为我听说使用同一个模块解压缩也很慢。

【问题讨论】:

您是否需要在纯 Python 中完成它,或者您是否可以满足于调用文件系统上的二进制文件(在 Python 中,您会使用 subprocess 模块)? 我不想在 Python 中这样做,因为纯 Python 方法太慢了。 您是否在 200GB 未压缩数据上从 shell 运行 gzip 程序?我预计在 90-100% 的 CPU 利用率下会花费相当多的挂钟时间 - 在我的 Windows 机器上,它每 GB 运行大约 1 分钟,而 Python gzip 模块每 GB 大约需要 2 分钟。 戴夫,是的,这是我追求的 2 分钟和 1 分钟之间的差异。 【参考方案1】:

ChristopheD 建议使用subprocess module 是对这个问题的适当回答。但是,我不清楚它是否会解决您的性能问题。您必须测量新代码的性能才能确定。

要转换您的示例代码:

import subprocess

p = subprocess.Popen("gzip -c > zipped.gz", shell=True, stdin=subprocess.PIPE)
p.communicate("Hello World\n")

由于需要向子进程发送大量数据,因此应考虑使用 Popen 对象的stdin 属性。例如:

import subprocess

p = subprocess.Popen("gzip -c > zipped.gz", shell=True, stdin=subprocess.PIPE)
p.stdin.write("Some data")

# Write more data here...

p.communicate() # Finish writing data and wait for subprocess to finish

this question 上的讨论也很有帮助。

【讨论】:

我验证了这种方法在 1GB 高度可压缩文件上的速度提高了 33%。与 gzip.open 相比,这是一个很好的改进。这是我用来测试它的代码: import subprocess text = "fjlaskfjioewru oijf alksfjlkqs jr jweqoirjwoiefjlkadsfj afjf\n" for i in xrange(1,25): text += text p = subprocess.Popen("gzip -c > zipped. gz", shell=True, stdin=subprocess.PIPE)` p.stdin.write(text) p.communicate() gzip.open 的时间:12.109u 1.194s 0:13.37 99.4% 0+0k 0+0io 0pf+ 0w 以上代码时间:8.379u 2.602s 0:10.17 107.8% 0+0k 0+0io 0pf+0w 一定要接受你最喜欢的答案:-)。我们都喜欢额外的代表。 出于好奇,当我使用这种方法而不是 gzip.open 时,大型测试的运行时间从 6h43m 下降到 4h31m。这是同一台机器上的苹果对苹果。这大约快了 33%,这正是我在较小的测试用例中看到的。谢谢大家! @bu11d0zer:你应该使用 pastebin 来处理这种事情:pastebin.com/2kZHsbFH【参考方案2】:

试试这样的:

from subprocess import Popen, PIPE
f = open('zipped.gz', 'w')
pipe = Popen('gzip', stdin=PIPE, stdout=f)
pipe.communicate('Hello world\n')
f.close()

【讨论】:

【参考方案3】:

使用 gzip module 是官方的一种方法,任何其他纯 python 方法都不太可能更快。尤其如此,因为您的数据大小排除了内存选项。最有可能的是,最快的方法是将整个文件写入磁盘并使用subprocess 对该文件调用 gz

【讨论】:

【参考方案4】:

确保在比较速度时使用相同的压缩级别。默认情况下,linux gzip 使用 6 级,而 python 使用 9 级。我在 Python 3.6.8 中使用 gzip 1.5 版对此进行了测试,从 mysql 转储中压缩了 600MB 的数据。使用默认设置:

python 模块耗时 9.24 秒,文件大小为 47.1 MB 子进程 gzip 耗时 8.61 秒,文件大小为 48.5 MB

将其更改为 6 级后使其匹配: python 模块耗时 8.09 秒,文件大小为 48.6 MB 子进程 gzip 耗时 8.55 秒,文件大小为 48.5 MB

# subprocess method
start = time.time()
with open(outfile, 'wb') as f:
    subprocess.run(['gzip'], input=dump, stdout=f, check=True)
print('subprocess finished after :.2f seconds'.format(time.time() - start))

# gzip method
start = time.time()
with gzip.open(outfile2, 'wb', compresslevel=6) as z:
    z.write(dump)
print('gzip module finished after :.2f seconds'.format(time.time() - start))

【讨论】:

【参考方案5】:

除了@srgerg 的回答之外,我还想通过禁用shell 选项shell=False 来应用相同的方法,这也是在@Moishe Lettvin 的回答中完成的,并在(https://***.com/a/3172488/2402577) 上推荐。

import subprocess
def zip():
    f = open("zipped.gz", "w")
    p1 = subprocess.Popen(["echo", "Hello World"], stdout=subprocess.PIPE)
    p2 = subprocess.Popen(["gzip", "-9c"], stdin=p1.stdout, stdout=f)
    p1.stdout.close()
    p2.communicate()
    f.close()

请不要说我最初将这个p1s 输出用于git diff

p1 = subprocess.Popen(["git", "diff"], stdout=subprocess.PIPE)

【讨论】:

以上是关于使用管道在 Perl 中将管道文件输出到 gzip 的 Python 等效项的主要内容,如果未能解决你的问题,请参考以下文章

在 perl 中关闭多个输出管道而不阻塞每个输出管道

Perl | Perl读取gzip压缩文件

PostgreSQL COPY 管道输出到 gzip 然后到 STDOUT

如何在 perl 中读/写命名管道?

在 Qt 4.7 中将标准输出管道传输到 QLabel

如果不重写Python / Perl脚本,我将如何在bash脚本中管道输出?