从另一个php文件执行php文件会占用过多的CPU

时间:2018-09-23 00:45:32

标签: php exec

我已经阅读了关于SO的其他类似标题的问题,但这不是这个问题的意思。我知道如何从另一个PHP脚本执行一个PHP脚本。问题是,当我这样做时,它将占用过多的CPU。我想知道如何减少这种情况。

我有一个简单的类似于前控制器的脚本,称为index.php。它处理来自客户端的GET请求,并根据传递的“ action”参数将请求发送到适当的文件以进行处理。例如,这是一个客户请求:

xhttp.open("GET", serverURL + "?action=doSomething" + "&userID=" + user.ID + "&time=" + lastServerTime, true);

index.php具有一个数组,该数组将“ action”参数映射到适当的文件:

exec('php ' . $url_map[$action] . ' "' . $parameter1 . '"' . ' "' . $parameter2 . '" 2>&1', $output, $return_value);

出于测试目的,我创建了一个PHP脚本,除了测量CPU利用率并将其转储到日志文件之外,什么也不做:

<?php

function varDumpToFile($parameter1) {
    $file = 'log.txt';
    $dump = $parameter1;

    $output = print_r($dump, true);

    file_put_contents($file, $output, FILE_APPEND | LOCK_EX);
}

varDumpToFile(`ps -eo pcpu,pid,user,args --no-headers| sort -t. -nk1,2 -k4,4 -r |head -n 5`);

?>

这将生成一个如下所示的日志文件:

9.0 3123052 user   /opt/cpanel/ea-php56/root/usr/bin/php cputest.php 10 147424 1537625595

很显然,PHP脚本不应占用9%的CPU。为了进行比较,我运行了相同的脚本,并通过GET请求直接访问了该脚本:

0.1 3186198 user   lsphp:ic_html/dev/php/cputest.php

0.1%更喜欢它。但是,为什么从另一个PHP脚本调用此PHP脚本会占用大量CPU?是因为我在执行PHP时必须执行PHP的“新实例”,这会产生大量开销?如果是这样,是否有一种方法可以使用“已运行的” PHP实例执行PHP脚本?还是有另一种方法?

2 个答案:

答案 0 :(得分:4)

我总是说“如果有疑问,请看PHP源代码”。例如In here。在进行exec时,您必须派生该过程,创建新流,从输入缓冲区中读取等等。

而且,虽然PHP是一种编译语言,但对于新创建的进程,必须运行操作码编译器以生成操作码(类似于Java字节码的指令),然后执行这些操作码。您可以阅读有关here的所有信息。最后,对每个fork分别运行两次编译器。

它是否值得您使用9%的CPU?我不知道。也许。也许不吧。谁知道。

“更好的解决方案”?升级到最新版本的PHP。不再支持PHP 5.6,安全更新将在3个月内停止。更好的解决方案-保留常规的面向对象和可维护的代码,而无需使用exec。 IMO,可以像您一样与exec一起玩。但是,如果这是您的生产代码,那么我将为那些会在您之后维护您的代码的人们祈祷。

答案 1 :(得分:2)

无论您以哪种方式运行应用程序mod_phpfpm,它们都依赖于准备好工作进程来管理您的请求。内置了流程管理:他们将尽最大努力使您指定的任意数量的工人处于空闲状态,并重用它们以避免出现此问题,而必须在最不希望的时间派生流程。

执行新流程不仅会产生开销,而且执行环境也会完全不同。如果查看您的php配置,将有几个php.ini文件,每个文件对应一个特定的环境。这意味着一个环境可以启用不同的模块或完全不同的配置。将cli脚本max_execution_timememory_limit设置为无限制的情况并不少见。这可能会影响服务器上的资源使用,但维护起来也很麻烦。

此外,由于您的脚本将在不同的执行环境中的全新进程中运行,因此将无法访问某些变量(例如$_SERVER$_POST)或诸如发送标头的功能

这就是所谓的共享内存。正如@Alex提到的,脚本必须进行编译。如果启用了操作码缓存(应该这样做),则在编译时将缓存字节码;如果生成的字节码已经存在,则可以跳过此编译过程。为此,您 需要有一个持续运行的进程,可以保留此内存。如果您要创建一个新进程,它将无法访问该共享区域,而必须自己完成所有编译。