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我将 imagemagick 使用的线程数(因为这是/是 OpenMP 中的一个错误?,参考)设置为 1 和exec('env MAGICK_THREAD_LIMIT=1');. 无论我设置什么值,PHP 的运行时都没有太大变化MAGICK_THREAD_LIMIT。无论如何,在这个版本中似乎没有 OpenMP 的错误,因为命令行执行的运行时是好的。

任何关于我如何改进上述命令的运行时间的建议都将不胜感激。

非常感谢您的帮助。

4

6 回答 6

15

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

当您使用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');

现在,如果您认为外壳本身可能是问题所在,因为制作外壳需要很长时间,请使用您知道几乎不需要时间的东西对其进行测试:

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

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

希望这可以帮助!

于 2013-01-11T13:04:54.663 回答
12

我从事计算机编程已经超过 56 年了,但这是我第一次遇到这样的错误。因此,我花了将近一周的时间试图了解perlphpviaexec执行perl程序与直接在命令行执行程序相比,执行速度要差 7 倍。作为这项工作的一部分,我还仔细研究了这个问题在网络上被提出的所有时间。这是我发现的:

(1) 这是一个在 2002 年首次报告的 bug,在随后的 11 年中一直没有修复。

(2) 该错误与与apache交互的方式有关php,因此这两个组织都将责任推给了对方。

(3) 错误在exec, system 或任何替代方案上是相同的。

(4) 该错误不取决于执行程序是perl,exe还是其他。

(5) 在 UNIX 和 Windows 上的 bug 是一样的。

(6) 该错误通常与图像无关imagemagick或与图像无关。我在完全不同的环境中遇到了这个错误。

(7) 该错误与 fork、shell、bash 等的启动时间无关。

(8) 更换apache服务的拥有者并不能修复该BUG。

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

当我遇到这个问题时,我有一个perl程序可以在 40 秒内执行,但exec需要 304 秒。我的最终解决方案是弄清楚如何优化我的程序,使其在 0.5 秒内直接执行或在 3.5 秒内执行exec。所以我从来没有解决这个问题。

于 2013-12-18T22:07:35.660 回答
6

@Philipp,因为您有 SSH,并且您的服务器允许访问,所以exec()我假设您也拥有对机器的完全 root 访问权限。

推荐用于单文件处理

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

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

即使不允许覆盖,您也可以从.htaccessif AllowOverrideis更改内存设置All

更改内存限制的另一种方法是在 PHP 运行时使用ini_set('memory_limit', 256);.

推荐用于批处理文件

运行转换的唯一好处exec()是,如果您不打算从中获取结果exec()并允许它异步运行:

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

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

性能说明

与在 PHP 中使用 GD/Imagick 相比,使用上面的代码exec运行async以处理单个文件将花费更多的处理器时间和更多的内存。时间/内存将由不影响 PHP 进程的不同进程使用(使访问者感觉网站移动得更快),但是存在内存消耗,并且在处理许多连接时会很重要。

于 2013-01-11T12:38:40.967 回答
5

这不是 PHP 错误,与 Apache/Nginx 或任何网络服务器无关。

我最近遇到了同样的问题,并查看了 PHP 源代码以检查 exec() 实现。

本质上,PHP 的 exec() 调用了 Libc 的 popen() 函数。

这里的罪魁祸首是 C 的 popen(),它似乎很慢。对“c popen slow”进行快速谷歌搜索会显示很多问题,比如你的问题。

我还发现有人在 C 中实现了一个名为 popen_noshell() 的函数来克服这个性能问题:

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

这是显示速度差异与 popen() 和 popen_noshell() 的屏幕截图:

速度差

PHP 的 exec() 使用常规的 popen() - 上面屏幕截图右侧的那个。如您所见,执行 C 的 popen() 时系统使用的 CPU 非常高。

我看到了这个问题的 2 个解决方案:

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

附加说明:

在搜索这个时,我发现了与 C 的同名的 PHP 函数:popen()

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

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

于 2018-03-01T07:54:22.040 回答
3

我遇到过这个问题,当通过命令行运行时大约需要 0.025 秒的图形处理命令在 PHP 中通过 exec() 调用时大约需要 0.3 秒。经过大量研究,似乎大多数人认为这是 apache 或 PHP 的问题。然后我尝试通过 CGI 脚本运行命令,完全绕过 PHP,得到了相同的结果。

因此看来问题一定是apache,所以我安装了lighttpd,得到了同样的结果!

经过一番思考和实验,我意识到这一定是处理器优先级的问题。因此,如果您希望命令以与命令行相似的速度运行,则必须按如下方式执行。

exec('echo "password" | sudo -S nice -n -20 command')

请注意:我知道对此会有各种各样的安全异议。我只是想简单地关注答案,你所要做的就是在你的命令之前添加 nice 。

于 2018-01-29T16:08:51.057 回答
2

当您调用execphp 时不会创建线程,它会创建一个新的子进程。创建一个新进程是很大的开销。

但是,当您使用 ssh 连接时,您只是传递了一个要执行的命令。您不是该程序的所有者,因此它作为您连接的用户执行。对于exec运行 PHP 的用户。

于 2013-01-11T12:11:53.467 回答