Sunday 11 April 2010

PHP benchmarks (part 3)

There is third and final post about PHP benchmarks.


Test:
double (")    vs.    single (') quotes

This one doesn't need to be tested, since single quotes are faster (but not so much as people would think), since double quotes strings do parse to find variables and special characters while single quotes don't.

Here is simple example to demonstrate that:

$var = 123;
$single_quotes = '$var\n';
$double_quotes = "$var\n";
echo $single_quotes;
echo $double_quotes;
echo 'Test';

And the result is:

$var\n123 Test

So, as you can see, single quote string isn't parsed, so PHP variables in that string aren't substituted with their string representation, and special characters like new line character is left as is - backslash followed by "n" character. While, string in double quotes is parsed and variables are replaced with their string representation.
Notice space between "123" and "Test" in place where should have been a new line. Actually, there is a new line and you can see that "Test" is in line next to line where "123" string is, if you check page source (Ctrl + U in Firefox, View -> Page source ... in some other browsers). This behaviour is characteristic of HTML language (all white space characters are represented as a space).

What would be interesting to test here? Well, single versus double quotes aren't correct subject to test since they do different thing and are being used in different situations (yes, both can be used in same situation, but then mostly you'll use single quotes since they are little faster). Here, the real subject of testing should be some real use case, I'll take, for example, some dummy SQL query building process:

$max = 100; 
$var1 = 123;
$var2 = 456;
$var3 = 789;
$var4 = 987;
$start = microtime();
for($i = 0; $i < $max; ++$i) {
$where = $var1 . ', ' . $var2 . ', ' . $var3 . ', ' . $var4;
}
echo 'Total test 1: ' . (microtime() - $start) . '<br>';

$var1 = 123;
$var2 = 456;
$var3 = 789;
$var4 = 987;
$start = microtime();
for($i = 0; $i < $max; ++$i) {
$where = "$var1,  $var2, $var3, $var4";
}
echo 'Total test 2: ' . (microtime() - $start) . '<br>';

$var1 = 123;
$var2 = 456;
$var3 = 789;
$var4 = 987;
$start = microtime();
for($i = 0; $i < $max; ++$i) {
$where = "{$var1}, {$var2}, {$var3}, {$var4}";
}
echo 'Total test 3: ' . (microtime() - $start) . '<br>';

$var1 = 123;
$var2 = 456;
$var3 = 789;
$var4 = 987;
$start = microtime();
$arr = array($var1, $var2, $var3, $var4);
for($i = 0; $i < $max; ++$i) {
$where = implode(', ', $arr);
}
echo 'Total test 4: ' . (microtime() - $start) . '<br>';

And the typical result is:

Total test 1: 0.000194

Total test 2: 0.000158

Total test 3: 0.000152

Total test 4: 0.000162

As you can see, using single quotes can sometimes even be slower (using implicitly ... with some other operation, to be precise). Here, slowness isn't product of single quotes, it's a product of string concatenation, which takes more time than double string parsing, or even "implode" of different array's parts (sometimes I use array to store values which I later implode in SQL statements, it's clean and simple solution).
So ... be aware of what you're doing and look at big picture not just micro-picture (or should I say ... micro-results). Don't just take benchmarks for granted - think (and benchmark) for yourself!


Next test is:

Test:
isset()    vs.    empty()    vs.    is_array()


Well, this test doesn't make much sense since these three things are checking for different things:

  • "isset" check if variable is set and is not NULL
  • "empty" checks if variable is empty. That is equivalent with boolean cast of particular variable. You should check PHP documentation regarding "empty" to see what values are considered to be empty (zero as a string for example is considered empty, empty string also, so check and remember, it's useful to know)
  • "is_array" checks if variable is array
So, for example, you can't use "empty" if you want to see if variable is an array. Use whatever is suitable, not whatever is the fastest.

Notice: "is_array" throws notice, while "isset" and "empty" does not. So, you should check if variable is set before checking if it's an array: 

if (isset($var) && is_array($var))

Doing checking this way, if variable is not set, "isset" will return "FALSE" and second evaluation argument ("is_array") won't be evaluated since PHP is optimized not to do that (FALSE && anything[TRUE or FALSE] is FALSE ... see logical operator truth table).

Notice's notice: it is often used OR operator in some cases. For example:

mysql_connect(...) OR die(...);
What does this mean? It's similar optimization as for AND logical operator, but it is different in one thing - second evaluation argument is evaluated only if the first one return FALSE. If first returns TRUE, the second one isn't evaluated since TRUE || anything is TRUE. You can see that in this simple example:

$test = 'Test';
TRUE OR ($test = 'OR');
echo $test; // echoes 'Test'


$test = 'Test';
FALSE OR ($test = 'OR');
echo $test; // echoes 'OR'

You can, for practice, do something similar for AND logical operator.


Next is final test in this series of posts:

Test:
switch/case    vs.    if/elseif
"==" vs. "===" 

There are some misconceptions here. "===" (strict) operator is faster than "==" (loose) operator, that for sure. But, they shouldn't be subject for testing, since they do different thing, for example:

"0" == FALSE // true
"0" === FALSE // false 

Strict comparison operator checks if variables are of same type and than it compares their values. Loose comparison operator behaves different, it checks if variable types are identical, if not it casts one variable type to other's type and then comparison is done.

Type conversion is done in next manner: if comparison involves number or a numerical string, they are converted to numbers and then compared numerically.

Notice: try this: var_dump("0" == "00");

Loose comparison is done in switch statements, so you should be aware of what you're comparing, because any string that doesn't can't be cast to numeric value is being cast to the zero!

 $abc = 'abc';
switch ($abc) {
case 0:
    echo 'Zero found';
    break;
case 'abc':
    echo 'abc found';
    break;
}

'abc' won't ever be found, since variable $abc will be converted to it's numeric representation and that would be 0 (zero) and it will be matched in first "case".


There are a lot more cases of comparison. You can find good references at PHP documentation:

http://php.net/manual/en/language.operators.comparison.php


Type of Operand 1 Type of Operand 2 Result
null or string string Convert NULL to "", numerical or lexical comparison
bool or null anything Convert to bool , FALSE < TRUE
object object Built-in classes can define its own comparison, different classes are uncomparable, same class - compare properties the same way as arrays (PHP 4), PHP 5 has its own explanation
string , resource or number string , resource or number Translate strings and resources to numbers, usual math
array array Array with fewer members is smaller, if key from operand 1 is not found in operand 2 then arrays are uncomparable, otherwise - compare value by value (see following example)
array anything array is always greater
object anything object is always greater





Now, you should only know how type are converted between them and that can also be found in PHP documentation:

http://www.php.net/manual/en/language.types.type-juggling.php#language.types.typecasting

For practice, you can make a lot of examples, here are some:

$one = '';
$two = 0;
$three = '0';
echo "'' == 0"; // true
var_dump($one == $two);
echo "'' == '0'"; // false, of course
var_dump($one == $three);
echo "0 == '0'"; // true
var_dump($two == $three); 


'1' == true
bool(true)

1 == 'true'
bool(false)

1 == true
bool(true)

'true' == true
bool(true)

'0' == true
bool(false)

'0' == false
bool(true)

'false' == true
bool(true)

'false' == false
bool(false)

etc. 


There is one more thing that could be tested - "echo" vs. "print" (with and without use of output buffering, see "ob_start" for example), but I'll leave that to the reader in hope he can make his own benchmark after this series of my blog posts. I hope you have learnt some things.


Here are some more optimization tips:


  • don't make your own functions, use PHP native wherever possible
  • use "@" - suppress warning operator - when you really need it - best is to avoid it, since it slows down application
  • when echo-ing more value, use "," instead of "." (echo $var1, $var2, $var3 - instead of - echo $var1 . $var2 . $var3 . ...)
  • don't use: $array[key] - use $array['key']. $array[key] is slower and error prone (if key is not constant ~ is not defined ~ it is considered as a string
  • use: if (isset($array['key'])) instead of: if (array_key_exists('key', $array)), but only if you are sure that any of $array values won't be NULL, since then 'key' will exist but "isset" will return FALSE
  • ++$i is faster than $i++
  • use "===" instead of "==" where ever possible (and not just for speed reasons)

And they are plenty more, I'm sure. I would like to hear any of them, so please send your advices to my email address or comment here.


At the end remember - these are only micro-optimizations. There are much better ways to optimize your application (if it's slow) than this tips. This is just a learning game to better know the language! I hope you've learnt something from this. :)

2 comments:

  1. In my tests that I have done, when setting an empty string, double quotes is faster than single quotes:

    $a = "";
    instead of
    $a = '';

    Of course it's such a small gain, I wouldn't worry about it.

    ReplyDelete
  2. Hi Ellis. :)

    I've tried it now. The benchmark results are almost the same and the result can vary so either can be faster. I think that can't be messured precisely (same as any of my experiments, but here the difference between two quotes is so tiny) since there are many background processes and any of them can slow down either of them.

    I would use single quotes, as for any string that is literal.



    Ivan

    ReplyDelete