Python子进程模块比命令(已弃用)慢得多

18

所以我写了一个脚本,使用命令行上的nc访问一堆服务器,最初我使用的是Python的commands模块并调用了commands.getoutput(),脚本运行大约需要45秒。由于commands已经被弃用,我想改用subprocess模块来完成所有操作,但现在脚本需要2m45s才能运行。有没有人知道为什么会这样?

之前的做法:

output = commands.getoutput("echo get file.ext | nc -w 1 server.com port_num")

现在我有

p = Popen('echo get file.ext | nc -w 1 server.com port_num', shell=True, stdout=PIPE)
output = p.communicate()[0]

感谢你提前提供的帮助!


1
当您从shell运行命令时,该命令需要多长时间? - wroniasty
只要服务器不超时,结果几乎是即时的。实际时间0m0.274秒 用户时间0m0.071秒 系统时间0m0.134秒 - thedrick
使用subprocess模块执行类似于'ls'、'uname -a'等的其他命令,是否也需要花费很长时间来执行? - wroniasty
不是的,从我看到的情况来看不是这样。而且当我在Python解释器中运行与上述相同的内容时,但在时间前加上subprocess,它实际上会更快一些。不确定为什么一旦纳入我的代码就会变慢... - thedrick
1
似乎问题不在于子进程模块... - wroniasty
显示剩余3条评论
2个回答

20

我认为 subprocess 的速度比 command 慢。并不是说这是你的脚本运行缓慢的唯一原因,但你应该查看 commands 的源代码。它不到100行,大部分工作都委托给来自os的函数,其中许多直接来自于c POSIX库(至少在POSIX系统中)。请注意,commands 仅适用于Unix,因此它不需要额外的工作来确保跨平台兼容性。

现在看看subprocess。它超过1500行,全部使用纯Python编写,并进行各种检查以确保一致的跨平台行为。基于此,我认为 subprocess 的运行速度比 commands 慢。

我测试了这两个模块,在一些非常基本的东西上,subprocess 几乎比 commands 慢了一倍。

>>> %timeit commands.getoutput('echo "foo" | cat')
100 loops, best of 3: 3.02 ms per loop
>>> %timeit subprocess.check_output('echo "foo" | cat', shell=True)
100 loops, best of 3: 5.76 ms per loop

Swiss提出了一些有助于脚本性能提升的好建议。但即使您应用了这些建议,仍需注意subprocess仍然是较慢的。

>>> %timeit commands.getoutput('echo "foo" | cat')
100 loops, best of 3: 2.97 ms per loop
>>> %timeit Popen('cat', stdin=PIPE, stdout=PIPE).communicate('foo')[0]
100 loops, best of 3: 4.15 ms per loop

假设你正在连续执行上述命令,这将累加,并占据一定的性能差异。

无论如何,我理解你的问题是关于subprocesscommand的相对性能,而不是关于如何加速脚本的问题。针对后者的问题,Swiss的回答更好。


啊,谢谢!我很高兴我不是疯了。我使用的是Python 2.6,所以我甚至没有使用check_output的选项。 - thedrick
2
我怀疑这并不是实际的问题所在。 - Swiss
@Swiss,我同意不使用shell会更好。我想我之前假设user1436110是出于必要而这样做的,并相应地回答了问题。仔细看后,我发现那可能并非如此。但即使应用了您建议的改进,subprocess仍然较慢。请参见上面的新计时。 - senderle
1
@senderle:subprocess 可能会更慢。然而,1或2毫秒的差异非常微小,并不能解释原问题提到的2分钟以上的差异。 - Swiss
@user1436110,只是想澄清一下:您是否正在下载大文件?大约多大?您的脚本中是否多次调用了 Popen?您在评论中提到的运行时间是针对一个 nc 调用还是多个调用? - senderle

20

这里似乎有至少两个不同的问题。

首先,您错误地使用了Popen。我看到的问题如下:

  1. 使用一个Popen生成多个进程。
  2. 将一个字符串作为args传入而不是拆分args。
  3. 使用shell而不是内置的communicate方法来向进程传递文本。
  4. 使用shell而不是直接生成进程。

以下是您的代码的修正版本:

from subprocess import PIPE

args = ['nc', '-w', '1', 'server.com', 'port_num']
p = subprocess.Popen(args, stdin=PIPE, stdout=PIPE)
output = p.communicate("get file.ext")
print output[0]

其次,您建议手动运行时它结束得更快,而通过子进程运行时则不然,这表明问题在于您没有向 nc 传递正确的字符串。可能发生的情况是服务器正在等待终止字符串以结束连接。如果您没有传递终止字符串,则连接可能保持打开状态直到超时。

手动运行 nc,找出终止字符串,然后更新传递给 communicate 的字符串。进行这些更改后,它应该运行得更快。


2
有什么原因需要使用2吗?为什么字符串版本被认为是低效的? - Mooncrater
1
嗨@Mooncrater,我不是OP,但我相信2)的建议是因为您想避免使用Shell=True。因此,您需要将命令添加为列表,而不是字符串。 - AllynH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接