使用管道在 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()
请不要说我最初将这个p1
s 输出用于git diff
:
p1 = subprocess.Popen(["git", "diff"], stdout=subprocess.PIPE)
【讨论】:
以上是关于使用管道在 Perl 中将管道文件输出到 gzip 的 Python 等效项的主要内容,如果未能解决你的问题,请参考以下文章