Hidden errors in PHP affect performance
By: Michal Táborský
Tags: PHP, Performance

I’ve come across an interesting thing recently, that I didn’t think mattered, but, as I’m about to demonstrate, it does.



So we all know about different levels of error reporting in PHP, right? We all know, that we must pay close attention to E_WARNING and above, and sometimes the occasional E_NOTICE carries some wisdom. (Almost) nobody cares about those annoying E_STRICT messages. In fact, even if you set the E_ALL reporting level, these messages are not displayed or logged, so maybe you even do not know they are there. I sure ignored them myself mostly.



Well, not anymore. Gather around kids, ’cause I’m about to tell you one thing that just might convince you to turn the E_STRICT reporting on. Even these seemingly insignificant messages negatively affect performance. What surprised me most was, that even if you have these messages suppressed, the penalty is still there.



But don’t take my word for it, let’s have some examples.





Statically calling non-static method



PHP5 is pretty open minded about the usage of static methods and properties. You can call any method from a class, without instantiating said class. The manual rightly states, that “Calling non-static methods statically generates an E_STRICT level warning.” Yeah, who cares, right? Well, you should.



Try this code:

$cnt=100000;



class One {

public function test1a() { return 1; }

public static function test1b() { return 1; }

}



$t1 = microtime(true);

for ($i=1; $i<=$cnt; $i++) One::test1a();

$t2 = microtime(true);

for ($i=1; $i<=$cnt; $i++) One::test1b();

$t3 = microtime(true);



$time1=$t2-$t1;

$time2=$t3-$t2;



printf("Test run 1 (bad): %1.3f ms

", $time1*1000);

printf("Test run 2 (good) : %1.3f ms

", $time2*1000);

printf("Avg. diff.: %1.3f us

", ($time1-$time2)*1000000/$cnt);

This simple code runs 100000 times a static call first to a plain public method and then a static call to a statically defined method of the same class. If I run this snippet on my average machine, I get this result:





Test run 1 (bad): 244.614 ms

Test run 2 (good) : 88.351 ms

Avg. diff.: 1.563 us





There you have it. Each static call of the non-static method costs extra 1.5 microsecond. Now, that might not be much, but if you stick it in a heavy loop, it adds up.



Accessing an undefined array element



One of the more annoying notices is the one where PHP tells you, that the array element you are trying to access is not defined. I bet, that is one of the big reasons why many people have these turned off. I mean, what is wrong with just testing like this?





if ($myarray[1]) {

do something...

} else {

do the other thing

}





What is wrong is, this statement will generate the notice. And--I'm sure you know where I am headed with this--it incurs a performance hit. Again, here's the proof.

<?php



$cnt=100000;



function test2a() {

$a = array();

$a[0] = 1;

if ($a[1]) {

return true;

} else {

return false;

}

}



function test2b() {

$a = array();

$a[0] = 1;

if ($a[0]) {

return true;

} else {

return false;

}

}



function test2c() {

$a[1] = 1;

if (isset($a[0]) && $a[0]) {

return true;

} else {

return false;

}

}



$t1 = microtime(true);

for ($i=1; $i<=$cnt; $i++) test2a();

$t2 = microtime(true);

for ($i=1; $i<=$cnt; $i++) test2b();

$t3 = microtime(true);

for ($i=1; $i<=$cnt; $i++) test2c();

$t4 = microtime(true);

$time1=$t2-$t1;

$time2=$t3-$t2;

$time3=$t4-$t3;

printf("Test run 1 : %1.3f ms

", $time1*1000);

printf("Test run 2 : %1.3f ms

", $time2*1000);

printf("Test run 3 : %1.3f ms

", $time3*1000);

printf("Avg. diff. 1-2: %1.3f us

", ($time1-$time2)*1000000/$cnt);

printf("Avg. diff. 2-3: %1.3f us

", ($time2-$time3)*1000000/$cnt);

The result on the same machine reads:

Test run 1 : 259.571 ms

Test run 2 : 160.604 ms

Test run 3 : 156.587 ms

Avg. diff. 1-2: 0.990 us

Avg. diff. 2-3: 0.040 us

This sample is a bit longer but still simple. The test2a() function reads an element of the array that is not defined. This would generate a notice, if we had them turned on.



Very similar function test2b() performs the same code but in this case the element is defined (this is a correct scenario and no warning or notice is generated). Hence the shorter runtime.



The third version of the same function if very careful and checks the existence of the array element before trying to use it. This might seem like an extra work, sometime unnecessary you might even say. The test however proves, that this extra test comes at virtually no cost performance wise and I dare say it’s worth it ten times over.



Conclusion



I have demonstrated what you might have suspected all along: sloppy coding harms performance. In this case, the ignored warnings and notices and strict messages that you think you sucessfully dealt with when you turned them off, will come back and haunt you. So, my advice is simple. Be brave! Put that error_reporting = E_ALL | E_STRICT in your php.ini and kill all the wee bugs once and for all.



PS: Obviously, you want to do that on you development box first, but I’m sure I don’t have to tell you THAT, right?