PHP exec()的性能表现

23
以下 PHP 代码返回大约 3.5 秒的运行时间(多次测量并取平均值):
$starttime = microtime(true);
exec('/usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');
$endtime = microtime(true);
$time_taken = $endtime-$starttime;

当我在ssh终端上运行相同的命令时,运行时间缩短到了约0.6秒(使用命令行工具time测量)。
imagemagick库的版本为:
Version: ImageMagick 6.7.0-10 2012-12-18 Q16 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2011 ImageMagick Studio LLC
Features: OpenMP

这个时间差的原因可能是什么?

在stackoverflow上类似问题的一个答案是Web服务器需要启动一个线程/ shell,导致开销增加。这真的是原因吗?我认为线程很轻量级,启动/终止不需要很长时间。

在调用exec之前,我使用exec('env MAGICK_THREAD_LIMIT=1');将imagemagick使用的线程数设置为1(因为这是OpenMP中的一个错误?参考)。无论我为MAGICK_THREAD_LIMIT设置什么值,PHP运行时都没有太大变化。无论如何,似乎在这个版本中OpenMP没有错误,因为命令行执行的运行时间是可以的。

是否有任何建议可以改进上述命令的运行时间,将不胜感激。

非常感谢您的帮助。


3
不知道你是否知道,你的PHP版本很可能有一个名为ImageMagick的扩展:http://php.net/imagick - nkr
我之所以通过命令行运行这个程序,是因为php.ini内存限制的原因。考虑到运行时间,使用ImageMagick扩展是否更好呢? - Philipp
@nkr PHP::GDPHP::imagick 更常见,作为默认的 Web 服务器配置。 - Mihai Stancu
@MihaiStancu:我听说GD提供的图像质量很差,而且@Philipp说他尝试通过“exec”使用ImageMagick,他应该尝试使用带有PHP扩展的ImageMagick。但总的来说,你是对的,GD更常用。 - nkr
6个回答

15

当您通过键盘或ssh登录Unix机器时,会创建一个新的shell实例。该shell通常类似于/bin/sh/bin/bash。它允许您执行命令。

当您使用exec()时,它也会创建一个新的shell实例。该实例执行您发送给它的命令,然后退出。

当您创建一个shell命令的新实例时,它具有自己的环境变量。所以如果您这样做:

exec('env MAGICK_THREAD_LIMIT=1');
exec('/usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');

然后你创建两个shell,第一个shell中的设置永远不会传递到第二个shell。要将环境变量传递到第二个shell中,你需要像这样做:

exec('env MAGICK_THREAD_LIMIT=1; /usr/local/bin/convert 1.pdf -density 200 -quality 85% 1.jpg');

现在,如果您认为Shell本身可能是问题,因为它花费太长时间来创建Shell,请使用一些您知道几乎不需要时间的内容进行测试:

$starttime = microtime(true);
exec('echo hi');
$endtime = microtime(true);
$time_taken = $endtime-$starttime;

在那时,你知道要尝试找一些方法使得 shell 实例化更快。

希望这有所帮助!


echo示例在我的机器上执行时间为0.005秒。 - jchook

13

我已经编程超过56年了,但这是我第一次遇到这样的故障。因此,我花了近一个星期的时间来理解通过execphp执行一个perl程序与直接在命令行执行perl程序时执行速度7倍更慢的原因。作为这个努力的一部分,我还翻阅了所有在网上提出这个问题的时间。以下是我发现的:

(1) 这是一个于2002年首次报告的漏洞,并在随后的11年中没有得到修复。

(2) 该漏洞与apachephp交互的方式有关,因此这两个组织都将责任推给对方。

(3) 无论是exec、system或其他替代方法,这个漏洞都是一样的。

(4) 该漏洞不依赖于所执行的程序是否是perlexe或其他。

(5) 该漏洞在UNIX和Windows上都是一样的。

(6) 该漏洞与imagemagick或图像本身无关。我在完全不同的环境中遇到了这个漏洞。

(7) 该漏洞与fork、shell、bash等启动时间无关。

(8) 更改apache服务的所有者无法修复此漏洞。

(9) 我不确定,但我认为它与调用子程序时大量增加的开销有关。

当我遇到这个问题时,我的一个perl程序在40秒内执行,但通过exec需要304秒。我的最终解决方案是找出如何优化我的程序,使其直接执行时间为0.5秒,通过exec执行时间为3.5秒。因此,我从未解决这个问题。


2
最后终于有人了!!!当每个人都用“必须启动一个 shell”来解释这种现象时,真的很烦人。但是事实上时间明显是成比例增加的,而不仅仅是“0.5秒加上相同”的壳体启动开销情况。现在,通过文件重定向,我成功地提高了一些速度(例如 dir > something.txt 然后从 php 读取文件),但还不够好。如果您找到了真正的答案,请让我知道! - dkellner
1
@dkellner 看看这个答案:https://dev59.com/o2Yq5IYBdhLWcg3wtCko#48505455。它适用于我的情况(从php运行git命令),但我正在寻找另一种方法,而不是sudo来获得进程优先级,从而更快地执行。 - tomwoods

6

@Philipp,既然你有SSH,并且你的服务器允许访问exec(),我就假定你也拥有完整的root权限。

单文件处理推荐方法

拥有机器的root权限意味着你可以更改/etc/php5/php.ini内存限制设置。

即使没有直接访问/etc/php5/php.ini的权限,你也可以通过在项目目录中创建一个新的php.ini文件来检查服务器是否支持覆盖php.ini指令。

即使不允许覆盖,如果AllowOverrideAll,你也可以从.htaccess更改你的内存设置。

另一种修改内存限制的方法是在PHP运行时使用ini_set('memory_limit', 256);进行设置。

批量文件处理推荐方法

通过exec()运行转换的唯一好处是,如果你不打算从exec()获得结果并允许其异步运行,则可以。

exec('convert --your-convert-options > /dev/null 2>/dev/null &');

如果您想批量处理许多文件,并且不想等待它们完成处理,也不需要对每个文件的处理进行确认,则上述方法通常很有帮助。

性能注意事项

使用上述代码使exec在处理单个文件时运行async将比在PHP中使用GD/Imagick花费更多的处理器时间和内存。时间/内存将被用于不影响PHP进程的不同进程(使访问者感觉网站移动得更快),但内存消耗是存在的,当处理许多连接时会计算。


5
这不是PHP的错误,与Apache/Nginx或任何Web服务器无关。
最近我也遇到了同样的问题,并查看了PHP源代码以检查exec()实现。
基本上,PHP的exec()调用Libc的popen()函数。
罪魁祸首是C的popen(),似乎非常慢。快速搜索“c popen slow”将显示许多类似于您的问题的问题。
我还发现有人实现了一个名为popen_noshell()的函数来克服这个性能问题:

https://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/

这里是一张截图,展示了使用popen()和popen_noshell()的速度差异:

Speed difference

PHP的exec()使用常规的popen() - 正如上面屏幕截图中右侧所示。当执行C语言的popen()函数时,系统使用的CPU非常高,您可以看到。

我看到此问题有两个解决方案:

  1. 创建一个实现popen_noshell的PHP扩展。
  2. 向PHP团队请求创建一组新的函数popen_noshell(),exec_noshell()等...这不太可能发生,我猜...

额外说明:

在搜索此问题时,我发现了与C语言同名的PHP函数:popen()

这很有趣,因为一个人可以异步地执行外部命令:pclose(popen('your command', 'r'));

这本质上具有与exec('your command &');相同的效果。


这似乎不太可能是问题的原因,因为使用PHP的exec()执行时间与正常shell的执行时间成比例增长。 - Johnathan Andersen

3
我曾经遇到过这个问题,一个图形处理命令在命令行下运行大约需要0.025秒,但是当通过PHP的exec()调用时,需要大约0.3秒。通过大量研究,似乎大多数人认为这是Apache或PHP的问题。然后我尝试了通过CGI脚本运行该命令,完全绕过了PHP,结果还是一样。
因此,问题必须是Apache的问题,所以我安装了lighttpd,结果还是一样!
经过一些思考和实验,我意识到这一定是处理器优先级的问题。因此,如果您希望您的命令具有类似命令行的速度,则必须按以下方式执行。
请注意:我知道会有各种安全方面的反对意见。我只想简单地强调答案就是在您的命令前加上nice。 exec('echo "password" | sudo -S nice -n -20 command')

2
当您调用exec时,php不会创建线程,而是创建一个新的子进程。创建新进程的开销很大。
但是当您使用ssh连接时,只是传递了一个要执行的命令。您不是该程序的所有者,因此它将以您连接的用户身份执行。对于exec来说,运行PHP的用户是该程序的所有者。

4
当你在命令行中输入命令时,命令行界面也会创建一个新的子进程。所以这本身不能成为原因。 - Jon
以apache用户和普通用户身份运行程序是不同的。我添加了一些关于为什么“ssh”技术执行更快的信息。 - Shiplu Mokaddim
2
我也认为创建一个进程不需要3秒钟。 - Esailija
@shiplu.mokadd.im:实际上它完全相同(除了有效权限、环境变量等等)。额外的文本并没有真正添加任何内容。 - Jon
exec() 创建了 两个 进程:一个 shell 进程,以及在该 shell 进程中调用的命令。 - KingCrunch
3
这不是真正的原因。它是一个持续的减速,不仅发生在命令启动时。从Shell运行1秒的东西,在PHP中会运行7秒,如果在shell中是2秒,那么在PHP中将是14秒,所以这不仅仅是一个启动延迟。它更像是摩擦力,一直在拖累整个过程。我很乐意弄清楚实际情况。 - dkellner

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