3

I got this code:

<?php
// Test results
$array1 = test('array_walk');
$array2 = test('array_walk_list_each');
$array3 = test('array_walk_foreach1');
$array4 = test('array_walk_foreach2');

// Check arrays for equal
var_dump($array1 == $array2, $array1 == $array3, $array1 == $array4);

// Test function 1
function array_walk_list_each(&$array, $function, $userData = null) {
    while ( list($key, $value) = each($array) )
        $function($array[$key], $key, $userData);
}

// Test function 2
function array_walk_foreach1(&$array, $function, $userData = null) {
    foreach ($array as $key => &$value )
        $function($value, $key, $userData);
}

// Test function 3
function array_walk_foreach2(&$array, $function, $userData = null) {
    foreach ($array as $key => $value )
        $function($array[$key], $key, $userData);
}

function some_function(&$value, $key, $userData) {
    $value = "$key => $userData";
}

function test($function, $count = 10000, $arrayElements = 1000) {
    echo $function, ' ... ';
    $array = array_fill(0, $arrayElements, "some text value");

    $timer = microtime(true);
    for( $i = 0; ++$i < $count; )
        $function($array, 'some_function', 'some user data');
    printf("%.3f sec\n", microtime(true) - $timer);

    return $array;
}

The Output of this is very difficult for me to understand:

array_walk ... 1.024 sec
array_walk_list_each ... 0.002 sec
array_walk_foreach1 ... 1.135 sec
array_walk_foreach2 ... 1.359 sec
bool(true)
bool(true)
bool(true)

the performance difference between these functions it's almost a joke.

How is it possible? am I doing something wrong?

I am running the script from the terminal using PHP 7.0

perodriguezl
  • 430
  • 3
  • 13

2 Answers2

2

foreach resets the internal array pointer before every run and moves it forward in every step. That means your test function will be called 10 000 000 as you probably expect. Array walk doesn't use the internal pointer at all but still treats every element on each call. That's why the time is comparable.

But each just increments the internal pointer after each use. It never resets it (Google the manual for more information). That means that you only modify the array once, and never enter the while loop on later runs. Since your some_function is idempotent your check for equal passes, but the time is much shorter.

Edit to add: The reset doesn't have to be explicit. Consider the this code:

function array_walk_list_each_copy(&$array, $function, $userData = null) {
  $a = $array;
  while ( list($key, $value) = each($a) ) 
    $function($array[$key], $key, $userData);
}

Each works on a copy of the array every time, and modifies the internal pointer of the copy, not the original. It won't beat the other functions, but be even slower, because of the copy-on-write overhead

jh1711
  • 2,288
  • 1
  • 12
  • 20
  • and how I ended with 4 equal arrays? – perodriguezl Aug 12 '17 at 15:28
  • Your function assigns 'some user data' to every value on every run. That means that the arrays don't really change after the first run (in mathmatics you call such a function idempotent). That's why all for arrays are equal. If you use a different function (e.g. `$value = $value++`) the arrays will be different. – jh1711 Aug 12 '17 at 15:40
2

Simply because each() needs to reset the array to iterate over it again. So you have a single execution within the loop function. While the others are iterating over it.

http://php.net/manual/en/function.each.php

Your result would produce only 1 iteration of 10000 rows, while the others would be 10000 iterations of 10000 rows.

$array = array_fill(0, 10000, uniqid('', false));
$fill = [];
$fill2 = [];
$timer = microtime(true);
for ($i = 0; $i < 10; $i++) {
    while (list($key, $value) = each($array)) {
        $fill[] = 'value';
    }
}
printf("While list each %.3f sec\n", microtime(true) - $timer);
$timer = microtime(true);
for ($i = 0; $i < 10; $i++) {
    foreach ($array as $key => $value) {
        $fill2[] = 'value';
    }
}
printf("Foreach %.3f sec\n", microtime(true) - $timer);
var_dump(count($fill), count($fill2));

Result: https://3v4l.org/bvNdO


To get identical results for all of the functions you would need to change the array_walk_list_each function.

 while ( list($key, $value) = each($array) ){
    $function($array[$key], $key, $userData);
 }
 reset($array);
Will B.
  • 17,883
  • 4
  • 67
  • 69
  • but look, I am getting the same 4 arrays in all the function results, how is it possible? – perodriguezl Aug 12 '17 at 15:27
  • @perodriguezl Your array is not altered after the first iteration in any of the functions. Nothing is added to them, you just change the initial set of 10,000 values, which the others do as well. If you were to do `$value = "$key => $userData" . ++$i;` you would see the incremented values don't match the other functions. – Will B. Aug 12 '17 at 17:38
  • You're effectively doing this: https://3v4l.org/Ohr76 when this is the actual result. https://3v4l.org/KIpKU – Will B. Aug 12 '17 at 17:44