Uploader

An all around general purpose file uploader for CakePHP. Packaged as a stand alone plugin with file validation, file scanning and support for a wide range of basic mime types.

Using Closures as callbacks within loops

Monday, December 20th 2010, 10:30pm
Topics: Other, PHP
Tags: Closure, Callback, Loop, Benchmark, Test, Case
Comments: 2
Permalink - Tinylink

In jQuery (and other Javascript frameworks) it is quite common to use closures (I refer to them as callback functions) to loop over arrays or objects. Even though it's a slow process and is much more efficient to use the built-in for loop, it got me thinking. Why not try and use the new Closure class in PHP 5.3 and see how well it performs within a loop? Suffice to say, I got some really really interesting results. Before I get into the details, here is the test script I wrote (the Benchmark class is merely a class I have written in the past).

<?php $data = range(0, 1000);
$clean = array();
 
function loop($array, Closure $closure) {
	if (!empty($array)) {
		foreach ($array as $key => $value) {
			$closure($key, $value);
		}
	}
}
 
Benchmark::start('loop');
 
foreach ($data as $key => $value) {
	$clean[$key] = $value;
}
 
loop($data, function($key, $value) {
	$clean[$key] = $value;
});
 
Benchmark::stop('loop');
echo Benchmark::display('loop'); ?>


I didn't get too in depth with my test cases and simply used Firefox and page refresh to get my results. I am running PHP 5.3.1 on a Windows 7 XAMPP installation with Apache and no caching. For benchmarking I was using microtime(true) and memory_get_usage().

I began testing with 4 different cases, each of which that changed the size of the $data array. I started with 1000 iterations, then 5000, then 10000 and lastly 100000. I would comment out the foreach/loop sections and run them one at a time (of course), and ran each test about 5 times to gather an average. Here are the results.

foreach:
1000	Time: 0.0010 / Memory: 137128 (Max: 689160)
5000	Time: 0.0052 / Memory: 706488 (Max: 1258528)
10000	Time: 0.0097 / Memory: 1412048 (Max: 1964120)
100000	Time: 0.0545 / Memory: 13849568 (Max: 14401656)

closure:
1000	Time: 0.0027 / Memory: 84984 (Max: 688832)
5000	Time: 0.0144 / Memory: 433672 (Max: 1258192)
10000	Time: 0.0267 / Memory: 866448 (Max: 1963744)
100000	Time: 0.1223 / Memory: 8525216 (Max: 14401256)


The first thing you will notice is the time it took to interpret the page. On average using a closure as a callback within a loop will take 2-3x longer to process. However, the interesting thing is that the memory usage is around 40% smaller (using more allocated memory) while using a closure than doing a foreach, yet the max allocated is nearly identical. I knew what the outcome would be before I even started it -- Javascript closures are the same way. Regardless it was a fun experiment and if anyone knows more about this, please shed some light on this topic for the rest of us!

But in closing I can sadly say, that no, you should not be using a closure for looping, just stick to the old fashion tried and true foreach or for loop.
Related Entries:

2 Comments

10 / 2 = ?
Allowed: [code] [b] [i] [u]
  • Herman Radtke
    hermanradtke.com
    Dec 20th 2010, 23:20
    1 I normally use whatever is more semantically appropriate. For example:
    $sum = array_reduce($set, function ($a, $b) { return $a + $b; });
    


    The above is more meaningful than a foreach loop of:
    $sum = 0;
    foreach ($set as $v) {
       $sum += $v;
    }
    


    Obviously the above example is trivial and we see clearly understand both code samples. However, the use of the array_reduce function implies that I am reducing the array into a single value. Even if I have a 6 dimensional array with a callback function 100 lines long, I know exactly what to expect as a result.

    As for performance trade-offs: I worry very little about writing the most optimized PHP code. If the database isn't the bottleneck it is usually some O(2^n) loop someone wrote that is going to be slow in any language.