skip to Main Content

I have read the other questions on SO with a similar title, but that’s not what this question is about. I know HOW to execute a PHP script from another PHP script. The problem is, when I do so, it uses far too much CPU. I would like to know how to reduce this.

I have a simple front-controller-like script called index.php. It processes GET requests from a client and depending on the “action” parameter passed, it sends the request to the appropriate file to handle it. For example, this is a client request:

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

index.php has an array that maps the “action” parameter to the appropriate file:

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

For testing purposes, I have created a PHP script that does nothing except measure CPU utilisation and dump it to a log file:

<?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`);

?>

This produces a log file that looks like this:

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

Clearly, a PHP script shouldn’t take 9% of CPU to execute. For comparison, I’ve run the same script directly accessing it via a GET request:

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

0.1% is more like it. But why does calling this PHP script from another PHP script use so much CPU? Is it because I have to execute a “new instance” of PHP when I exec PHP, which has a lot of overhead? If so, is there a way to exec a PHP script using an “already running” instance of PHP? Or is there another way of doing this?

2

Answers


  1. I always say “when in doubt, look at PHP source code”. In here, for instance. While doing exec, you have to fork the process, create a new stream, read from the input buffer, etc.

    And also, while PHP is a compiled language, for the newly forked process, you must run the opcode compiler to generate opcodes (instructions similar to Java bytecode) and then execute those. You can read all about it here. In the end you run the compiler twice, for each fork separately.

    Is it worth 9% of your CPU? I have no idea. Maybe. Maybe not. Who knows.

    “Better solution”? Upgrade to latest version of PHP. PHP 5.6 is not supported anymore and security updates will cease in 3 months. Even better solution – keep a normal object-oriented and maintainable code without using exec. IMO, it’s okay to play around with exec like you are. But if it’s your production code, I pray for the souls of those, who would maintain your code after you.

    Login or Signup to reply.
  2. Whatever which way you run your application be mod_php or fpm, they rely on having worker processes ready to manage your request. Process management is built in: they will do their best to keep as many workers idle as you specify and reuse them to avoid exactly this problem, having to fork processes at the least desirable moment.

    Not only there’s overhead on executing new processes, but the execution environment will be completely different too. If you look into your php configuration there will be several php.ini files, one for each specific environment. This means that one environment could have different modules enabled or different configuration outright. It’s not uncommon to have cli scripts max_execution_time or memory_limit set to unlimited. This can affect resource usage on your server, but it’s also a pain to maintain.

    Also, since your scripts will be running in a brand new process in a different execution environment, this won’t have access to some variables (like $_SERVER or $_POST) or capabilities like sending headers.

    And there’s this thing called shared memory. As @Alex mentions, scripts have to be compiled. If you have opcode cache enabled (which you should) the bytecode gets cached when compiled and this compilation process can be skipped if the resulting bytecode it’s there already. For this to work you need to have a persistent running process that can keep this memory around. If you are creating a new process it can’t access this shared area and has to do the compilation all by itself.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search