Avoiding a temporary array not worth it


Recently I changed some code in PHPCR and thus also TYPO3CR that made it necessary to change some client code. Where I used to create an iterator and then append() items to it in a loop, I was now forced to prepare an array to hand over to the iterator's constructor.

So I created code like this:

foreach ($object->getData() as $item) {
$stuff[] = $item->getInfo();
}
$iterator = new \Foo\BarIterator($stuff);

That seemed ugly, and today I stumbled across a post in the php-internals mailing list that was asking for performance issues on a solution that avoids loops like the one above by using an anonymous function.

$iterator = new \Foo\BarIterator(
array_map(function($item) {return $item->getInfo(); }, $object->getData())
);

It seemed elegant, so I created some tests to check for time end memory impact of that solution. The result was - sadly - not what I had hoped. That way does not save on memory but takes longer. While elegance and readability are values in itself, this is (at least for me at that part of my code) not worth the compromise.

Here is the test code I used, for three variants of creating the iterator:

// test.php
function microtime_float() {
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}

$data = array();
for ($i=0; $i < 100000; $i++) {
$data[] = str_repeat('123-', 100);
}

$time_start = microtime_float();
$copy = array();
foreach ($data as $value) {
$copy[] = $value;
}
$a = new ArrayIterator($copy);

echo (microtime_float() - $time_start) . " seconds used\n";
echo memory_get_peak_usage() . " bytes used\n";

// test1.php
function microtime_float(){
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}

$data = array();
for ($i=0; $i < 100000; $i++) {
$data[] = str_repeat('123-', 100);
}

$time_start = microtime_float();
$copy = array_map(function($value) { return $value; }, $data);
$a = new ArrayIterator($copy);

echo (microtime_float() - $time_start) . " seconds used\n";
echo memory_get_peak_usage() . " bytes used\n";

// test2.php
function microtime_float() {
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}

$data = array();
for ($i=0; $i < 100000; $i++) {
$data[] = str_repeat('123-', 100);
}

$time_start = microtime_float();
$a = new ArrayIterator(array_map(function($value) { return $value; }, $data));

echo (microtime_float() - $time_start) . " seconds used\n";
echo memory_get_peak_usage() . " bytes used\n";

And these are the results (I ran the tests mutliple times, the results where almost identical across runs):

kmac:Sites karsten$ php test.php
0.081659078598022 seconds used
77379144 bytes used
kmac:Sites karsten$ php test1.php
0.12520813941956 seconds used
77379344 bytes used
kmac:Sites karsten$ php test2.php
0.10409712791443 seconds used
77379048 bytes used

Comments

  1. Gravatar

    Iterators are not functional! They're very much an OOP concept (since state is stored internally), especially in PHP's formulation.

  2. Gravatar

    I think the most elegant solution would be to develop a simple MappingIterator class. Such a thing seems to be missing in the SPL Iterators (http://www.php.net/~helly/php/ext/spl/).



    Using an iterator that simply stores the mapping function and the inner iterator lets you get rid of the temporary array. And additionally its a bit more functional style programming :-)

Leave a reply

Karsten Dambekalns

Creative Code Engineer

Contact / Impressum