How Significant Is PHP Function Call Overhead?

Josh Johnson picture Josh Johnson · Jul 31, 2013 · Viewed 8.4k times · Source

I'm relatively new to PHP and slowly learning the idiosyncrasies specific to the language. One thing I get dinged by a lot is that I (so I'm told) use too many function calls and am generally asked to do things to work around them. Here's two examples:

// Change this:
} catch (Exception $e) {
  print "It seems that error " . $e->getCode() . " occured";
  log("Error: " . $e->getCode());
}

// To this:
} catch (Exception $e) {
  $code = $e->getCode();
  print "It seems that error " . $code . " occured";
  log("Error: " . $code);
}

2nd Example

// Change this:
$customer->setProducts($products);

// To this:
if (!empty($products)) {
  $customer->setProducts($products);
}

In the first example I find that assigning $e->getCode() to $code ads a slight cognitive overhead; "What's '$code'? Ah, it's the code from the exception." Whereas the second example adds cyclomatic complexity. In both examples I find the optimization to come at the cost of readability and maintainability.

Is the performance increase worth it or is this micro optimization?

I should note that we're stuck with PHP 5.2 for right now.

I've done some very rough bench tests and find the function call performance hit to be on the order of 10% to 70% depending on the nature of my bench test. I'll concede that this is significant. But before that catch block is hit there was a call to a database and an HTTP end point. Before $products was set on the $customer there was a complex sort that happened to the $products array. At the end of the day does this optimization justify the cost of making the code harder to read and maintain? Or, although these examples are simplifications, does anybody find the 2nd examples just as easy or easier to read than the first (am I being a wiener)?

Can anyone cite any good articles or studies about this?

Edit:

An example bench test:

<?php
class Foo {
        private $list;
        public function setList($list) {
                $this->list = $list;
        }
}

$foo1 = new Foo();

for($i = 0; $i < 1000000; $i++) {
        $a = array();
        if (!empty($a))
                $foo1->setList($a);
}
?>

Run that file with the time command. On one particular machine it takes an average of 0.60 seconds after several runs. Commenting out the if (!empty($a)) causes it to take an average of 3.00 seconds to run.

Clarification: These are examples. The 1st example demonstrates horrible exception handling and a possible DRY violation at the expense of a simple, non-domain-specific example.

Answer

user3183198 picture user3183198 · Feb 10, 2014

PHP function call overhead is precisely 15.5355%.

:) Just stirring the pot.

Seriously, here are a couple great links on the subject:

Is it possible to have too many functions in a PHP application?

functions vs repeated code

The code maintainability versus speed discussions at these links address the (maybe more important) question implied by the OP, but just to add a little data that may also be pertinent and hopefully useful to people who come across this thread in the future, here are results from running the below code on a 2011 Macbook Pro (with very little drive space and too many programs running).

As noted elsewhere, an important consideration in deciding whether to call a function or put the code "in-line" is how many times the function will be called from within a certain block of code. The more times the function will be called, the more it's worth considering doing the work in-line.

Results (times in seconds)

Call Function Method | In-Line Method | Difference | Percent Different

1,000 iterations (4 runs)

0.0039088726043701 | 0.0031478404998779 | 0.00076103210449219 | 19.4694

0.0038208961486816 | 0.0025999546051025 | 0.0012209415435791 | 31.9543

0.0030159950256348 | 0.0029480457305908 | 6.7949295043945E-5 | 2.2530

0.0031449794769287 | 0.0031390190124512 | 5.9604644775391E-6 | 0.1895

1,000,000 iterations (4 runs)

3.1843111515045 | 2.6896121501923 | 0.49469900131226 | 15.5355

3.131945848465 | 2.7114839553833 | 0.42046189308167 | 13.4249

3.0256152153015 | 2.7648048400879 | 0.26081037521362 | 8.6201

3.1251409053802 | 2.7397727966309 | 0.38536810874939 | 12.3312

function postgres_friendly_number($dirtyString) {
    
    $cleanString = str_ireplace("(", "-", $dirtyString);
    $badChars = array("$", ",", ")");
    $cleanString = str_ireplace($badChars, "", $cleanString);
    
    return $cleanString;
    
}


//main
$badNumberString = '-$590,832.61';

$iterations = 1000000;

$startTime = microtime(true);
for ($i = 1; $i <= $iterations; $i++) {
    $goodNumberString = postgres_friendly_number($badNumberString);
}
$endTime = microtime(true);
$firstTime = ($endTime - $startTime); 

$startTime = microtime(true);
for ($i = 1; $i <= $iterations; $i++) {
    $goodNumberString = str_ireplace("(", "-", $badNumberString);
    $badChars = array("$", ",", ")");
    $goodNumberString = str_ireplace($badChars, "", $goodNumberString);
}
$endTime = microtime(true); 
$secondTime = ($endTime - $startTime); 

$timeDifference = $firstTime - $secondTime;
$percentDifference = (( $timeDifference / $firstTime ) * 100);