My favorite IDE has support for PHP again!
It's still in early stage of development, but it looks promising. Feel free to try it, it usable at this stage.
http://www.aptana.com/products/studio3/download
Test it, report bugs - and make suggestions! Suggestions will help Aptana team to make this product better.
https://aptanastudio.tenderapp.com/discussions/suggestions
Showing posts with label PHP. Show all posts
Showing posts with label PHP. Show all posts
Tuesday, 20 July 2010
Sunday, 11 April 2010
PHP benchmarks (part 3)
There is third and final post about PHP benchmarks.
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:
And the result is:
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:
And the typical result is:
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:
Well, this test doesn't make much sense since these three things are checking for different things:
You can, for practice, do something similar for AND logical operator.
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:
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' 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
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:
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:
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. :)
Labels:
PHP
Friday, 9 April 2010
PHP benchmarks (part 2)
Let's continue ...
Next test is:
This test is good in terms of optimization (precalculation of array's size is worth, because size of an array is being calculated every time at new iteration), but one thing has to be mentioned here, assumption is that array doesn't change in terms of adding or deleting items, because "count" would be different from $size and these two loops would do different things. So, you should be aware of what you're doing because you could get unexpected results.
You should be careful with this, because they are not identical. I'll show you in one example:
And the results are:
As you can see, variable's reference acts as described in my previous post - as variable alias (one more name). As for object, object's reference is just an identifier of particular object. There can be multiple identifiers, just like references, but difference is that references are closely bonded to variable and object's reference to object's identifier. Here is small graphic illustration:
Here is one more example to illustrate that, I'll use "unset" to show the difference (and to show difference between "$a = NULL" and "unset($a)"):
And the results are:
Now, let's go back to previous example and see what happens when setting variables to "NULL".
Here are results once more:
So...
$c = NULL
unset($c)
You can draw other situations for practice. :)
When I first observed how "unset($a)" and "$a = NULL" work, it seemed to me that they behave in identical way:
This produces "NULL", so I thought "$a = NULL" would produce same result. But they behave different in the background. I haven't looked at PHP's internals, but I suppose "unset" deletes variable from symbol table, while setting to "NULL" doesn't delete variable from symbol table, it just setts it's value to "NULL". This can be illustrated by next example:
And result is:
Unset $a
$a = NULL
Be careful when using "isset". From PHP documentation (http://www.php.net/manual/en/function.isset.php):
This means - is defined in symbol table and it's value is not NULL. There is a difference between this two conditions in terms of producing a notice as we can see here:
First "echo" produces notice, while second one does not (because there is no $a variable in symbol table - it is not defined as notice says).
That's all for this time, more info coming!
Next test is:
Test
Is it worth the effort to calculate the length of the loop in advance?
E.g. "$size=count($array); for ($i=0; $i<$size; $i++)" instead of "for ($i=0; $i<count($array);$i++)"
This test is good in terms of optimization (precalculation of array's size is worth, because size of an array is being calculated every time at new iteration), but one thing has to be mentioned here, assumption is that array doesn't change in terms of adding or deleting items, because "count" would be different from $size and these two loops would do different things. So, you should be aware of what you're doing because you could get unexpected results.
Test
$obj = new SomeClass() vs. $obj =& new SomeClass() using the =&-ref-operator
You should be careful with this, because they are not identical. I'll show you in one example:
class A {public $p = 1;}
$a = new A;$b = $a;$c = & $a;
echo $a->p . '<br>';
$b->p++;
echo $a->p . '<br>';
$c->p++;
echo $a->p . '<br>';
echo '<br>##############<br>';
echo '<br>$a = NULL;<br>';
$a = new A;$b = $a;$c = & $a;
$a = NULL;
var_dump($a);echo '<br>';
var_dump($b);echo '<br>';
var_dump($c);echo '<br>';
echo '<br>##############<br>';
echo '<br>$b = NULL;<br>';
$a = new A;$b = $a;$c = & $a;
$b = NULL;
var_dump($a);echo '<br>';
var_dump($b);echo '<br>';
var_dump($c);echo '<br>';
echo '<br>##############<br>';
echo '<br>$c = NULL;<br>';
$a = new A;$b = $a;$c = & $a;
$c = NULL;
var_dump($a);echo '<br>';
var_dump($b);echo '<br>';
var_dump($c);echo '<br>';
And the results are:
1
2
3
##############
$a = NULL;
NULL
object(A)#2 (1) { ["p"]=> int(1) }
NULL
##############
$b = NULL;
object(A)#1 (1) { ["p"]=> int(1) }
NULL
object(A)#1 (1) { ["p"]=> int(1) }
##############
$c = NULL;
NULL
object(A)#2 (1) { ["p"]=> int(1) }
NULL
As you can see, variable's reference acts as described in my previous post - as variable alias (one more name). As for object, object's reference is just an identifier of particular object. There can be multiple identifiers, just like references, but difference is that references are closely bonded to variable and object's reference to object's identifier. Here is small graphic illustration:
$a = $c => $obj
$b => $obj
Here is one more example to illustrate that, I'll use "unset" to show the difference (and to show difference between "$a = NULL" and "unset($a)"):
echo '<br>##############<br>';
echo '<br>unset $a<br>';
$a = new A;$b = $a;$c = & $a;
unset($a);
var_dump($a);echo '<br>';
var_dump($b);echo '<br>';
var_dump($c);echo '<br>';
echo '<br>##############<br>';
echo '<br>unset $b<br>';
$a = new A;$b = $a;$c = & $a;
unset($b);
var_dump($a);echo '<br>';
var_dump($b);echo '<br>';
var_dump($c);echo '<br>';
echo '<br>##############<br>';
echo '<br>unset $c<br>';
$a = new A;$b = $a;$c = & $a;
unset($c);
var_dump($a);echo '<br>';
var_dump($b);echo '<br>';
var_dump($c);echo '<br>';
And the results are:
##############
unset $a
NULL
object(A)#1 (1) { ["p"]=> int(1) }
object(A)#1 (1) { ["p"]=> int(1) }
##############
unset $b
object(A)#2 (1) { ["p"]=> int(1) }
NULL
object(A)#2 (1) { ["p"]=> int(1) }
##############
unset $c
object(A)#1 (1) { ["p"]=> int(1) }
object(A)#1 (1) { ["p"]=> int(1) }
NULL
Now, let's go back to previous example and see what happens when setting variables to "NULL".
Here are results once more:
##############
$a = NULL;
NULL
object(A)#2 (1) { ["p"]=> int(1) }
NULL
##############
$b = NULL;
object(A)#1 (1) { ["p"]=> int(1) }
NULL
object(A)#1 (1) { ["p"]=> int(1) }
##############
$c = NULL;
NULL
object(A)#2 (1) { ["p"]=> int(1) }
NULL
So...
$a = $c => $obj
$b => $obj
$c = NULL
$a = $c => NULL
$b => $obj
unset($c)
$a => $obj
$b => $obj
You can draw other situations for practice. :)
When I first observed how "unset($a)" and "$a = NULL" work, it seemed to me that they behave in identical way:
$a = 'Test';
unset($a);
var_dump($a);
This produces "NULL", so I thought "$a = NULL" would produce same result. But they behave different in the background. I haven't looked at PHP's internals, but I suppose "unset" deletes variable from symbol table, while setting to "NULL" doesn't delete variable from symbol table, it just setts it's value to "NULL". This can be illustrated by next example:
$a = 'Test';
echo '<pre>';print_r(get_defined_vars());echo '</pre>';
echo '<br>Unset $a<br>';unset($a);
echo '<pre>';print_r(get_defined_vars());echo '</pre>';
echo '<br>$a = NULL<br>';
$a = 'Test';
$a = NULL;
echo '<pre>';print_r(get_defined_vars());echo '</pre>';
And result is:
Array
(
[GLOBALS] => Array
(
[GLOBALS] => Array
*RECURSION*
[_POST] => Array
(
)
[_GET] => Array
(
)
[_COOKIE] => Array
(
)
[_FILES] => Array
(
)
[a] => Test
)
[_POST] => Array
(
)
[_GET] => Array
(
)
[_COOKIE] => Array
(
)
[_FILES] => Array
(
)
[a] => Test
)
Unset $a
Array
(
[GLOBALS] => Array
(
[GLOBALS] => Array
*RECURSION*
[_POST] => Array
(
)
[_GET] => Array
(
)
[_COOKIE] => Array
(
)
[_FILES] => Array
(
)
)
[_POST] => Array
(
)
[_GET] => Array
(
)
[_COOKIE] => Array
(
)
[_FILES] => Array
(
)
)
$a = NULL
Array
(
[GLOBALS] => Array
(
[GLOBALS] => Array
*RECURSION*
[_POST] => Array
(
)
[_GET] => Array
(
)
[_COOKIE] => Array
(
)
[_FILES] => Array
(
)
[a] =>
)
[_POST] => Array
(
)
[_GET] => Array
(
)
[_COOKIE] => Array
(
)
[_FILES] => Array
(
)
[a] => )
Be careful when using "isset". From PHP documentation (http://www.php.net/manual/en/function.isset.php):
Determine if a variable is set and is not NULL.
This means - is defined in symbol table and it's value is not NULL. There is a difference between this two conditions in terms of producing a notice as we can see here:
echo $a;$a = NULL;echo $a;
First "echo" produces notice, while second one does not (because there is no $a variable in symbol table - it is not defined as notice says).
That's all for this time, more info coming!
Labels:
PHP
Tuesday, 6 April 2010
PHP benchmarks (part 1)
Recently I've stumbled upon several PHP benchmark tests. I've found that a lot of these tests were inaccurate or based on false conceptions.
Unfortunately, none of them mentioned what PHP version are they using (and maybe some extra stuff could be useful also). I'll use PHP 5.3/Apache 2.2 on my Windows XP SP3.
Let's bring up some light. I'll begin with loops!
LOOPS
Question: What is the best way to loop a hash array?
Answer: In short - there is no one best way to loop an array. It depends on what do you intend to do with array (read, write/modify).
In one benchmark regarding array values reading (and not writing) these were the tests:
And this tests and conclusions are incorrect since they don't do identical thing.
For example, one can benchmark:
++$i; VS $i++;
since result will be correct. But benchmarking:
$j = $i++; VS $j = ++$i;
isn't correct since final result isn't identical (the latter $j will be greater).
So, one should be aware of what he is benchmarking, so he could draw right conclusions from benchmark results.
Now ... why above mentioned benchmarks aren't correct?
Because they are comparing different things. Take "foreach" loop for example:
How could this be compared? First two aren't identical since there is one extra assignment ($key). The third ... well, I'm not sure what author intended to do with this, but it isn't good at all since there are more operations involved beside looping - creating $tmp array's new element, getting $aHash element with $key key and aliasing $aHash array as new array element.
One important misconception is there:
So ... in short - using reference will modify original array in one writes to referenced array's value, not using won't (that's why values are being copied when writing/modifying, it's language optimization so you don't have to worry about that).
If someone doesn't understand & operator ... it simply creates variable alias (one more name). Try to imagine that like some thing that has more than one name:
Here, your fruit is 'Apple'.
Actually, I am You in the background. :)
Try this:
My tests are divided into two sections. The first one uses array's key, the second one doesn't. Why to test like this? Because of above mentioned fairness - loop has to do identical thing in different ways so results can be compared. One should store array's key into variable ($key) when it will be used.
$value = $hashC[$key];
Typical results are:
Someone will probably notice "@reset" (see "PHP @" and "PHP reset"). Yes, that adds some extra time to the "while" loop (but it doesn't make a difference). "@reset" is commonly used before "while" loop, so I think it's better to put it in the test because it shows real use case.
Tests without array's key:
The typical results are:
Let's move on to the "modify loop" ...
Here are my tests:
Typical results are:
So, one can see that the missing "foreach" loop is actually the fastest! :)
CONCLUSION
There is no need to complicate things. Just use what is proper to use:
Unfortunately, none of them mentioned what PHP version are they using (and maybe some extra stuff could be useful also). I'll use PHP 5.3/Apache 2.2 on my Windows XP SP3.
Let's bring up some light. I'll begin with loops!
LOOPS
Question: What is the best way to loop a hash array?
Answer: In short - there is no one best way to loop an array. It depends on what do you intend to do with array (read, write/modify).
In one benchmark regarding array values reading (and not writing) these were the tests:
+ 100 % 1: foreach($aHash as $val); Total time: 0[ms]
+ 1196 % 2: while(list(,$val) = each($aHash)); Total time: 1[ms]
+ 2176 % 3: foreach($aHash as $key=>$val); Total time: 2[ms]
+ 3017 % 4: while(list($key,$val)= each($aHash)); Total time: 3[ms]
+ 2300 % 5: foreach($aHash as $key=>$val) $tmp[] = & $aHash[$key]; Total time: 3[ms]
+ 2461 % 6: while(list($key) = each($aHash)) $tmp[]=&$aHash[$key]; Total time: 3[ms]
+ 819 % 7: Get key-/ value-array: foreach($aHash as $key[]=>$val[]); Total time: 1[ms]
+ 864 % 8: Get key-/ value-array: array_keys() / array_values() Total time: 1[ms]
+ 839 % 9: STRANGE: This is the fasetest code when using the the &-ref-operator (to avoid copying)
$key = array_keys($aHash);
$size = sizeOf($key);
for ($i=0; $i<$size; $i++) $tmp[] = &$aHash[$key[$i]]; Total time: 1[ms]
And this tests and conclusions are incorrect since they don't do identical thing.
For example, one can benchmark:
++$i; VS $i++;
since result will be correct. But benchmarking:
$j = $i++; VS $j = ++$i;
isn't correct since final result isn't identical (the latter $j will be greater).
So, one should be aware of what he is benchmarking, so he could draw right conclusions from benchmark results.
Now ... why above mentioned benchmarks aren't correct?
Because they are comparing different things. Take "foreach" loop for example:
1: foreach($aHash as $val);
3: foreach($aHash as $key=>$val);
5: foreach($aHash as $key=>$val) $tmp[] = &$aHash[$key];
How could this be compared? First two aren't identical since there is one extra assignment ($key). The third ... well, I'm not sure what author intended to do with this, but it isn't good at all since there are more operations involved beside looping - creating $tmp array's new element, getting $aHash element with $key key and aliasing $aHash array as new array element.
One important misconception is there:
STRANGE: This is the fasetest code when using the the &-ref-operator (to avoid copying)& - reference operator actually doesn't prevent copying (actually, it has different semantic meaning and behaviour ... see "PHP reference"). When looping through array, it's value copy isn't made unless array is being modified (see "PHP lazy copying" or "PHP copy on write"). So, reading array values won't make a copy and using &-reference operator in foreach loop that doesn't modify an array actually slows down looping since variable alias has to be created in symbol table (see "PHP symbol table").
So ... in short - using reference will modify original array in one writes to referenced array's value, not using won't (that's why values are being copied when writing/modifying, it's language optimization so you don't have to worry about that).
If someone doesn't understand & operator ... it simply creates variable alias (one more name). Try to imagine that like some thing that has more than one name:
$myFruit = 'Apple';
$yourFruit = & $myFruit;
var_dump($yourFruit);
//prints 'Apple'
Here, your fruit is 'Apple'.
Actually, I am You in the background. :)
Try this:
$yourFruit = 'Orange';
var_dump($myFruit);
//prints 'Orange'So, you can see these variables acts as one.
My tests are divided into two sections. The first one uses array's key, the second one doesn't. Why to test like this? Because of above mentioned fairness - loop has to do identical thing in different ways so results can be compared. One should store array's key into variable ($key) when it will be used.
$key = $keys[$i];$hashA = array();$hashB = array();$hashC = array();$a = 'a';$b = 'b';$c = 'c';$max = 10000;
for ($i = 0; $i < $max; ++$i) {$hashA[$a . $i] = $i;}
$start = microtime();
foreach ($hashA as $key => $value) {}
echo microtime() - $start, '<br><br>';
# NEXT TESTfor ($i = 0; $i < $max; ++$i) {$hashB[$b . $i] = $i;}
$start = microtime();@reset($hashB);while(list($key, $value) = each($hashB)) {}
echo microtime() - $start, '<br><br>';
# NEXT TESTfor ($i = 0; $i < $max; ++$i) {$hashC[$c . $i] = $i;}
$start = microtime();
$key = array_keys($hashC);$size = count($key);for ($i=0; $i<$size; ++$i) {
$value = $hashC[$key];
}
echo microtime() - $start, '<br><br>';
Typical results are:
0.002062
0.014654
0.005652
Someone will probably notice "@reset" (see "PHP @" and "PHP reset"). Yes, that adds some extra time to the "while" loop (but it doesn't make a difference). "@reset" is commonly used before "while" loop, so I think it's better to put it in the test because it shows real use case.
Tests without array's key:
$hashA = array();
$hashB = array();
$hashC = array();
$a = 'a';
$b = 'b';
$c = 'c';
$max = 10000;
for ($i = 0; $i < $max; ++$i) {
$hashA[$a . $i] = $i;
}
$start = microtime();
foreach ($hashA as $value) {}
echo microtime() - $start, '<br><br>';
# NEXT TEST
for ($i = 0; $i < $max; ++$i) {
$hashB[$b . $i] = $i;
}
$start = microtime();
@reset($hashB);
while(list(, $value) = each($hashB)) {}
echo microtime() - $start, '<br><br>';
# NEXT TEST
for ($i = 0; $i < $max; ++$i) {
$hashC[$c . $i] = $i;
}
$start = microtime();
$keys = array_keys($hashC);
$size = count($keys);
for ($i=0; $i<$size; ++$i) {
$value = $hashC[$keys[$i]];
}
echo microtime() - $start, '<br><br>';
The typical results are:
0.000784
0.014069
0.005436
Let's move on to the "modify loop" ...
+ 272 % 1: foreach($aHash as $key=>$val) $aHash[$key] .= "a"; Total time: 3[ms]These tests are more or less correct, but one version of "foreach" loop is missing. If we want to modify array's values, why not use & operator? In above tests it's used to read values, but not for modifying values - and that is wrong in semantic context. It should be used only when we need to modify original array and not opposite (just when reading array's values).
+ 128 % 2: while(list($key) = each($aHash)) $aHash[$key] .= "a"; Total time: 1[ms]
+ 100 % 3: STRANGE: This is the fasetest code :
$key = array_keys($aHash);
$size = sizeOf($key);
for ($i=0; $i<$size; $i++) $aHash[$key[$i]] .= "a"; Total time: 1[ms]
Here are my tests:
$hashA = array();$hashB = array();$hashC = array();$hashD = array();
$a = 'a';$b = 'b';$c = 'c';$d = 'd';$max = 10000;
for ($i = 0; $i < $max; ++$i) {$hashA[$a . $i] = $i;}
$start = microtime();
foreach ($hashA as $key => $value) {$hashA[$key] = 'A';}
echo microtime() - $start, '<br><br>';
# NEXT TESTfor ($i = 0; $i < $max; ++$i) {$hashB[$b . $i] = $i;}
$start = microtime();@reset($hashB);while(list($key) = each($hashB)) {$hashB[$key] = 'B';}
echo microtime() - $start, '<br><br>';
# NEXT TESTfor ($i = 0; $i < $max; ++$i) {$hashC[$c . $i] = $i;}
$start = microtime();
$keys = array_keys($hashC);$size = count($keys);
for ($i=0; $i<$size; ++$i) {$hashC[$keys[$i]] = 'C';}
echo microtime() - $start, '<br><br>';
# NEXT TESTfor ($i = 0; $i < $max; ++$i) {$hashD[$d . $i] = $i;}
$start = microtime();
foreach ($hashD as &$value) {$value = 'D';}
echo microtime() - $start, '<br><br>';// dump $hashD if you don't believe it's modified//var_dump($hashD);
Typical results are:
0.007736
0.015848
0.006368
0.002416
So, one can see that the missing "foreach" loop is actually the fastest! :)
CONCLUSION
There is no need to complicate things. Just use what is proper to use:
- if one reads array values use "foreach($array as $value)" or "foreach($array as $key => $value) if one needs array's keys also
- if one writes/modifies original array values use "foreach($array as &$value)" or "foreach($array as $key => &$value) if one needs array's keys, so that values are written using referencing variable $value (and not $array[$key])
FINAL WORDS
If you want to optimize your PHP scripts by changing loop type ("while" to "foreach") - don't do that, especially if your application isn't slow. These optimizations are micro-optimizations and they are made as conclusion from results under certain environment and conditions and may not produce same results in some other environment and conditions.
In real world situations, PHP will rarely be the slowest part of your application - database design is most common point where to search for speeding up your application ... amount of HTML, JS, CSS are also place where you could speed up user experience. For more frequently used applications, there is APC, load balancing, caching and so on.
This should be guidelines how to write PHP scripts from basics and to understand some of PHP basics. More will come up soon!
Labels:
PHP
Subscribe to:
Posts (Atom)