Micro-Optimization and PHP ???

Yes really!

Gotten to the point where adding that next caching for some database query simply stopped being helpful?

No clue why your crappy WordPress plugin takes up 50% or more of the page load time, even though you cut the database queries down to next to no query time?

This is for you …

When to Micro-Optimize ???

Easy … whenever there is no other way out of your performance problem. Sure hardware is a ton cheaper than developer time … but every now and then your project simply does not allow for that hardware upgrade for whatever reason …

Shipping your product to a bunch of customers maybe?

Already running the fastest CPU out there?

Your developers love you and work for free?

… so many possible reasons for why you might be reading this …

So let’s get to it …

No Free Lunch …

So you did it … your static code analysis tool is finally happy. All methods are less than 40 lines or whatever arbitrary maximum method length you though will make your code look nice.

Everything is nicely encapsulated in a million classes, every action your code takes fires of a trivial to understand series of small objects interacting all nice and readable … unfortunately your customers started to brutalize Google for a leaner competitor meanwhile … those ungrateful simpletons … not showing the least bit of appreciation for your code quality, for your readability … not even for your separation of concerns …

What did you think … that the load time of your site is just those darn database queries ?

… or maybe those darn regular expressions ?

… or is it all in your caching now? All those serialize/unserialize operations getting the best of you?

Sure those things are slow at times … we’ll look into those some other time though. For now, let’s try to find out how much we’re actually paying for all those tiny functions, methods, objects and redundant variables that make everything so nice and readable.

… those aren’t free … not at all.

Getting down to it …

Step 1: So not free eh? How much are they then? ( Gathering Data )

Well it depends … let’s gather some data I guess …

Since we might be dealing with the case of a broad and very heterogeneous user population, lets gather that data across multiple PHP versions. While we’re at it, we can also stop you from using all that crazy profiling I suppose … so lets also play with XDebug while we’re at it …

So multiple setups … good thing it’s 2015 and we have Docker to play with now.

So help yourself to some working docker daemon and the code we’re gonna be using here.

Hint:

git clone https://github.com/original-brownbear/php-performance-demos.git

So once we have that code, let’s run it. Since we’re mostly concerned with the effects of inlining ( or rather not inlining ) things here. That .sh file seems like a good start.

./inline.sh

Bam! … that’s enough data for one day I guess …

PHP 5.4


It took 1.4025449752808 s without inlining the function and with redundant local variables inside the function
It took 0.84425115585327 s without inlining the function and without redundant variables inside the function
It took 0.80232501029968 s with inlining the function and with redundant local variables
It took 0.51220011711121 s with inlining the function and without redundant local variables
It took 2.572212934494 s with using a class method and with redundant local variables
It took 1.9973518848419 s with using a class method and without redundant local variables
It took 2.8596611022949 s with using a class method with method parameters and with redundant input variables
It took 2.8738570213318 s with using a class method with method parameters and without redundant input variables
It took 5.4805200099945 s with using a class method with constructor parameters and without property declarations
It took 4.4061641693115 s with using a class method with constructor parameters and with private property declarations
It took 4.3610110282898 s with using a class method with constructor parameters and with protected property declarations
It took 4.3442859649658 s with using a class method with constructor parameters and with public property declarations

PHP 5.4 + XDebug


It took 6.619166135788 s without inlining the function and with redundant local variables inside the function
It took 6.9019331932068 s without inlining the function and without redundant variables inside the function
It took 2.0546748638153 s with inlining the function and with redundant local variables
It took 1.1751098632812 s with inlining the function and without redundant local variables
It took 9.0551729202271 s with using a class method and with redundant local variables
It took 8.1946280002594 s with using a class method and without redundant local variables
It took 10.072288990021 s with using a class method with method parameters and with redundant input variables
It took 9.2914588451385 s with using a class method with method parameters and without redundant input variables
It took 19.139733076096 s with using a class method with constructor parameters and without property declarations
It took 15.34263086319 s with using a class method with constructor parameters and with private property declarations
It took 15.49893116951 s with using a class method with constructor parameters and with protected property declarations
It took 15.300618171692 s with using a class method with constructor parameters and with public property declarations

PHP 5.5


It took 1.4747610092163 s without inlining the function and with redundant local variables inside the function
It took 0.86752104759216 s without inlining the function and without redundant variables inside the function
It took 0.95179986953735 s with inlining the function and with redundant local variables
It took 0.52045392990112 s with inlining the function and without redundant local variables
It took 2.7302191257477 s with using a class method and with redundant local variables
It took 2.0185148715973 s with using a class method and without redundant local variables
It took 2.8453660011292 s with using a class method with method parameters and with redundant input variables
It took 2.8968069553375 s with using a class method with method parameters and without redundant input variables
It took 5.2499659061432 s with using a class method with constructor parameters and without property declarations
It took 4.3478300571442 s with using a class method with constructor parameters and with private property declarations
It took 4.1988000869751 s with using a class method with constructor parameters and with protected property declarations
It took 4.214898109436 s with using a class method with constructor parameters and with public property declarations

PHP 5.5 + XDebug


It took 6.2536900043488 s without inlining the function and with redundant local variables inside the function
It took 4.9655790328979 s without inlining the function and without redundant variables inside the function
It took 2.0919289588928 s with inlining the function and with redundant local variables
It took 1.2138221263885 s with inlining the function and without redundant local variables
It took 8.6731350421906 s with using a class method and with redundant local variables
It took 7.4445428848267 s with using a class method and without redundant local variables
It took 8.8504319190979 s with using a class method with method parameters and with redundant input variables
It took 8.0068590641022 s with using a class method with method parameters and without redundant input variables
It took 15.685569047928 s with using a class method with constructor parameters and without property declarations
It took 14.428926944733 s with using a class method with constructor parameters and with private property declarations
It took 14.412018060684 s with using a class method with constructor parameters and with protected property declarations
It took 14.306435108185 s with using a class method with constructor parameters and with public property declarations

PHP 5.6


It took 1.4295959472656 s without inlining the function and with redundant local variables inside the function
It took 0.85520005226135 s without inlining the function and without redundant variables inside the function
It took 0.74695014953613 s with inlining the function and with redundant local variables
It took 0.49425292015076 s with inlining the function and without redundant local variables
It took 2.4544260501862 s with using a class method and with redundant local variables
It took 1.9607548713684 s with using a class method and without redundant local variables
It took 2.6875011920929 s with using a class method with method parameters and with redundant input variables
It took 2.6686630249023 s with using a class method with method parameters and without redundant input variables
It took 5.1677548885345 s with using a class method with constructor parameters and without property declarations
It took 4.1540579795837 s with using a class method with constructor parameters and with private property declarations
It took 4.1646270751953 s with using a class method with constructor parameters and with protected property declarations
It took 4.131668806076 s with using a class method with constructor parameters and with public property declarations

PHP 5.6 + XDebug


It took 6.1721510887146 s without inlining the function and with redundant local variables inside the function
It took 4.9106600284576 s without inlining the function and without redundant variables inside the function
It took 2.1572380065918 s with inlining the function and with redundant local variables
It took 1.2649810314178 s with inlining the function and without redundant local variables
It took 8.7360029220581 s with using a class method and with redundant local variables
It took 7.3417930603027 s with using a class method and without redundant local variables
It took 8.9390790462494 s with using a class method with method parameters and with redundant input variables
It took 8.3251278400421 s with using a class method with method parameters and without redundant input variables
It took 15.677669048309 s with using a class method with constructor parameters and without property declarations
It took 14.916995048523 s with using a class method with constructor parameters and with private property declarations
It took 14.911762952805 s with using a class method with constructor parameters and with protected property declarations
It took 14.528133869171 s with using a class method with constructor parameters and with public property declarations

PHP 7


It took 0.55066180229187 s without inlining the function and with redundant local variables inside the function
It took 0.23726606369019 s without inlining the function and without redundant variables inside the function
It took 0.37661790847778 s with inlining the function and with redundant local variables
It took 0.10790801048279 s with inlining the function and without redundant local variables
It took 1.2261588573456 s with using a class method and with redundant local variables
It took 0.92833209037781 s with using a class method and without redundant local variables
It took 1.3132491111755 s with using a class method with method parameters and with redundant input variables
It took 1.2214460372925 s with using a class method with method parameters and without redundant input variables
It took 2.2280390262604 s with using a class method with constructor parameters and without property declarations
It took 1.7811279296875 s with using a class method with constructor parameters and with private property declarations
It took 1.8180849552155 s with using a class method with constructor parameters and with protected property declarations
It took 1.8068678379059 s with using a class method with constructor parameters and with public property declarations

PHP 7 + XDebug


It took 5.1429510116577 s without inlining the function and with redundant local variables inside the function
It took 3.9954431056976 s without inlining the function and without redundant variables inside the function
It took 1.5582900047302 s with inlining the function and with redundant local variables
It took 0.64883804321289 s with inlining the function and without redundant local variables
It took 6.7732870578766 s with using a class method and with redundant local variables
It took 5.6968550682068 s with using a class method and without redundant local variables
It took 6.8934459686279 s with using a class method with method parameters and with redundant input variables
It took 6.4292969703674 s with using a class method with method parameters and without redundant input variables
It took 12.728615045547 s with using a class method with constructor parameters and without property declarations
It took 11.849714994431 s with using a class method with constructor parameters and with private property declarations
It took 12.050687074661 s with using a class method with constructor parameters and with protected property declarations
It took 11.997341871262 s with using a class method with constructor parameters and with public property declarations

So how did we do that? … we just ran this script leveraging Docker’s awesomeness.

for PHP_VERSION in "5.4" "5.5" "5.6" "7"
do
	echo -e "\nPHP ${PHP_VERSION}\n"
	docker run -t --rm -v "$PWD":/src php:${PHP_VERSION}-cli php /src/inline.php
	echo -e "\nPHP ${PHP_VERSION} + XDebug\n"
	docker run -t --rm -v "$PWD":/src originalbrownbear/php:${PHP_VERSION}-cli-xdebug php /src/inline.php
done

Mount the current working dir containing the inline.php test script into various docker containers and execute it in exactly the same way on each of them.

The test script itself just does one simple thing in various ways, it concatenates the strings ‘a’ and ‘b’ a bunch of times and depending on the test either very directly or buried deep in the stack …

So, what can we learn from this, let’s see …

Step 2: … WTF is This ??? ( Making Sense of the Data )

Step 2.1: Turn off XDebug …

Without even going into the details of each test run, you should be able to see why XDebug is probably the worst thing to have running while optimizing performance. It significantly skews the relative performance of various solutions to the same problem.

A few examples from the above data for PHP 5.6:

Using redundant local variables comes with a 1.43/0.86 ~ 1.66 times slower execution for concatenating ‘a’ and ‘b’ when running:

<?php

$a = 'a';
$b = 'b';

$a . $b;

instead of outright running:

<?php

'a' . 'b';

when XDebug is not loaded.

Load XDebug and the advantage of removing those redundant variable assignments goes down to 6.17/4.91 ~ 1.26 speedup/slowdown.

The trouble with XDebug is not that it slows down overall execution, the trouble is it skewing relative performance relative to production systems! Either avoid using its profiling when micro-optimizing or at least be mindful of this effect!

Step 2.2: Understand the data …

Step 2.2.1 Redundant Local Variables are Slow and Hard to Read

Lets not get into the readability thing here … this is settled anyhow … You use the variable only once ? So why use a variable ? Why would you want me, the poor dude having to read your code, to be forced to understand that you’re just using that variable as a long-winded way of adding a comment? You don’t need to save that data to anywhere, you’re only using it once … so please make the code tell me that when I read it …

Also … apart from me being able to better understand your code … more importantly your PHP interpreter will be able to better understand it …

Just by the numbers the above example of adding pointless variable assignment to the concatenating of those two strings comes with a slowdown, depending on the PHP version of:

PHP 5.4: 66%

PHP 5.5: 68%

PHP 5.6: 66%

PHP 7.0: 139%!

So why is that so … everyone on StackOverflow tells me that these assignments are ‘optimized away’ anyhow?

First off … guess who does that optimizing and needs time for it … yes your CPU …

Second of all … PHP is constantly optimizing things behind the scenes and getting ever better at it as time goes by. Now it’s relatively trivial to see ‘a’ . ‘b’ and figure out that this will always come out to ‘ab’, reserve the respective amount of memory and get all the other crazy weird things behind the scenes right. But thing about optimizing $a . $b … there’s nothing to optimize there unless the interpreter knows what $a and $b are beforehand. It cannot know this without executing the code though for obvious reasons … ( cough … Halting problem ) Good thing you’re a human and know what’s going on in your code better than the machine … be so kind and tell it all that you know as directly as you can. It might reciprocate and help your customers as quickly as it can at some point down the line :)

Step 2.2.2 Function Calls ain’t Free …

The next thing to understand is, that function calls are not free at all compared to just running code line by line. Someone has to find out and keep track of what function means what … Sure this is all in some hash table with very fast lookup and very strong scaling behavior … then again if you do this a few thousand times it does add up …

So lets start out with the numbers on how using some function like

<?php

function concat_function(){
  return 'a' . 'b';
}

compares to just good old …

<?php

'a' . 'b';

Depending on the PHP Version the slow down comes out to:

PHP 5.4: 65%

PHP 5.5: 67%

PHP 5.6: 73%

PHP 7.0: 118%!

Again … the newer your PHP version the more of the under the hood progress made on the interpreter is wasted by adding additional function calls that mess up optimizations.

This is not to say that you should get carried away creating monstrous functions spanning hundreds of lines, rather see if that small function inside some long loop really needs to even be a function/method ? Maybe it can be inlined since it’s used in one spot only anyhow?

Also for even more obvious reasons … at least stop doing this to your coworkers:

<?php

function foo(){
	return bar();
}

Seriously … why is this still happening in so many projects on this planet? … Sure sometimes it’s just an outright compatibility/needing that alias thing … mostly this is just stupid though ..

Step 2.2.3 Objects do cost a lot!

Given you know about the expenses incurred from redundant variable assignments and function calls this shouldn’t come as a surprise to you, object instantiation and class methods come at a significant cost. More on this some other time, just keep above numbers in mind when coding your long loops.

Something like the below example ( obviously way over the top ), surely won’t be an issue used like 10 times throughout your app. Use it in that 10k iterations loop and it will show though …

<?php

class ConcaterConstructParamDeclared{
	private $a;
	private $b;
	public function __construct($a, $b){
		$this->a = $a;
		$this->b = $b;
	}
	public function concat(){
		return $this->a . $this->b;
	}
}

Step 3: Uff … won’t do it again … Promise! ( Avoiding Overhead when it matters )

Just knowing the above should give you a strong starting point to optimize away some of the demonstrated overhead from your code where it matters.

Instead of going overboard with these things though … keep your code clean, keep things modular … when the time comes to speed up those long loops or whatever, you’ll find them in no time.