Performance of Foreach, Array_Map With Lambda and Array_Map With Static Function

Performance of foreach, array_map with lambda and array_map with static function

FWIW, I just did the benchmark since poster didn't do it. Running on PHP 5.3.10 + XDebug.

UPDATE 2015-01-22 compare with mcfedr's answer below for additional results without XDebug and a more recent PHP version.


function lap($func) {
$t0 = microtime(1);
$numbers = range(0, 1000000);
$ret = $func($numbers);
$t1 = microtime(1);
return array($t1 - $t0, $ret);
}

function useForeach($numbers) {
$result = array();
foreach ($numbers as $number) {
$result[] = $number * 10;
}
return $result;
}

function useMapClosure($numbers) {
return array_map(function($number) {
return $number * 10;
}, $numbers);
}

function _tenTimes($number) {
return $number * 10;
}

function useMapNamed($numbers) {
return array_map('_tenTimes', $numbers);
}

foreach (array('Foreach', 'MapClosure', 'MapNamed') as $callback) {
list($delay,) = lap("use$callback");
echo "$callback: $delay\n";
}

I get pretty consistent results with 1M numbers across a dozen attempts:

  • Foreach: 0.7 sec
  • Map on closure: 3.4 sec
  • Map on function name: 1.2 sec.

Supposing the lackluster speed of the map on closure was caused by the closure possibly being evaluated each time, I also tested like this:


function useMapClosure($numbers) {
$closure = function($number) {
return $number * 10;
};

return array_map($closure, $numbers);
}

But the results are identical, confirming that the closure is only evaluated once.

2014-02-02 UPDATE: opcodes dump

Here are the opcode dumps for the three callbacks. First useForeach():



compiled vars: !0 = $numbers, !1 = $result, !2 = $number
line # * op fetch ext return operands
---------------------------------------------------------------------------------
10 0 > EXT_NOP
1 RECV 1
11 2 EXT_STMT
3 INIT_ARRAY ~0
4 ASSIGN !1, ~0
12 5 EXT_STMT
6 > FE_RESET $2 !0, ->15
7 > > FE_FETCH $3 $2, ->15
8 > OP_DATA
9 ASSIGN !2, $3
13 10 EXT_STMT
11 MUL ~6 !2, 10
12 ASSIGN_DIM !1
13 OP_DATA ~6, $7
14 14 > JMP ->7
15 > SWITCH_FREE $2
15 16 EXT_STMT
17 > RETURN !1
16 18* EXT_STMT
19* > RETURN null

Then the useMapClosure()


compiled vars: !0 = $numbers
line # * op fetch ext return operands
---------------------------------------------------------------------------------
18 0 > EXT_NOP
1 RECV 1
19 2 EXT_STMT
3 EXT_FCALL_BEGIN
4 DECLARE_LAMBDA_FUNCTION '%00%7Bclosure%7D%2Ftmp%2Flap.php0x7f7fc1424173'
21 5 SEND_VAL ~0
6 SEND_VAR !0
7 DO_FCALL 2 $1 'array_map'
8 EXT_FCALL_END
9 > RETURN $1
22 10* EXT_STMT
11* > RETURN null

and the closure it calls:


compiled vars: !0 = $number
line # * op fetch ext return operands
---------------------------------------------------------------------------------
19 0 > EXT_NOP
1 RECV 1
20 2 EXT_STMT
3 MUL ~0 !0, 10
4 > RETURN ~0
21 5* EXT_STMT
6* > RETURN null

then the useMapNamed() function:


compiled vars: !0 = $numbers
line # * op fetch ext return operands
---------------------------------------------------------------------------------
28 0 > EXT_NOP
1 RECV 1
29 2 EXT_STMT
3 EXT_FCALL_BEGIN
4 SEND_VAL '_tenTimes'
5 SEND_VAR !0
6 DO_FCALL 2 $0 'array_map'
7 EXT_FCALL_END
8 > RETURN $0
30 9* EXT_STMT
10* > RETURN null

and the named function it calls, _tenTimes():


compiled vars: !0 = $number
line # * op fetch ext return operands
---------------------------------------------------------------------------------
24 0 > EXT_NOP
1 RECV 1
25 2 EXT_STMT
3 MUL ~0 !0, 10
4 > RETURN ~0
26 5* EXT_STMT
6* > RETURN null

PHP - Is array_map faster than foreach?

I believe this answers your question and is current as of 2015-01-22

Performance of foreach, array_map with lambda and array_map with static function

array_map although more elegant is sadly slower in PHP. Particularly if using it with a closure.

array_map vs loop and operation

It is due to the difference between Callback functions and normal functions.

In the second one, iteration of array using foreach, each iteration calls "insert" function and wait for the execution (function return control) and proceed to next iteration.

But in the array_map function, "insert" happens as callback function, it calls "insert" and don't wait for the result and call insert with next item in the array. So it is faster.

Hope it helps.

How to use array_map with a function that takes more than one parameter?

You can try like this

<?php
function replace($array)
{
$replace_res = str_replace('_', ' ', $array);
return $replace_res;
}

$array = array("12_3","a_bc");
$result = array_map("replace", $array);
print_r($result);
?>

PHP, foreach doesnt return an array but array_map with same code returns an array

Because array_map() iterates over all elements in the array.... the foreach() would do the same except that your return is jumping out of it on the first iteration.

function registered_users(){
$users = [];
$file_user = file('userdata.php');
foreach ($file_user as $user) {
$users[] = explode(':',$user);
}
return $users;
}

EDIT

In response to your question "Why doesn't a return from array_map terminate the iteration?"

Because array_map() is a function that loops/iterates every element in the array, executing a "callback" function against each element. Your return is in the "callback" function, which acts on one individual array element at a time, and is called multiple times by array_map(), once for each element of the array in turn.

The return in your "callback" is simply returning a modified value for that one individual element (the current element in the array_map() loop) to the array_map() function.... it's telling array_map() what the new element value should be.

The array_map() function itself can't be interrupted: it will then continue iterating over the next element, sending that in turn to the "callback" function until it has done so for every element in the array.

Why does this code use array_filter and array_map to filter an array of integers?

There would be no difference at all if you use the second one as a replacement of first one.

Why: This is due to behaviour of intval function.

intval Returns integer value of variable on success, or 0 on failure. Empty arrays return 0, non-empty arrays return 1.

Point to be noticed: 0 is considered as false, and any other non-zero integer value considered as true in PHP.

array_filter filters data based on return value of callable function which should be either true of false.

Now in your situation you're calling array_filter in two manners.

array_filter( array_map( 'intval', (array) $_POST['product_ids'] ) )

  • In this you're passing an array of integers to array_filter as array_map will loop over array and convert each value to corresponding integer by calling intval function.
  • Now as there is no parameter passed in array_filter so by default empty function will be used and only numeric but not zero(0) values remains.

array_filter( (array) $_POST['product_ids'], 'intval' )

  • In this you are calling array_filter but intval as callable function, which will convert each value to corresponding integer. Main catch is here, if corresponding value is 0 then it will be false, otherwise true, which is equivalent to empty function in this case.

Note: This doesn't apply to other functions and would not create same results. See this by comparing intval and is_string function.

// Input data
$entry = [
0 => 'foo',
1 => false,
2 => -1,
3 => null,
4 => '',
5 => '0',
6 => 0,
];

// comparison for intval function
print_r(array_filter( array_map( 'intval', $entry ) ));
print_r(array_filter( $entry, 'intval' ));

Output:
Array
(
[2] => -1
)
Array
(
[2] => -1
)

// but if you try another function like is_string
print_r(array_filter( array_map( 'is_string', $entry ) ));
print_r(array_filter( $entry, 'is_string' ));
Output:
Array
(
[0] => 1
[4] => 1
[5] => 1
)
Array
(
[0] => foo
[4] =>
[5] => 0
)

Using array_map with multi-argument function?

Now I'll start off by saying that I'd probably never use this in any real projects, but this is an interesting challenge/question and T.Todua's answer works but using $GLOBALS can be avoided.

My position is that array_walk_recursive() is a better suited function versus recursively calling array_map() -- after all, array_walk_recursive() was specifically designed to visit leaf nodes and avoid the tedium of checking the current item's type as "array". use() is effective in passing the function string into the recursive function's scope.

*Note: You could only pass the function string as a string in a SUPER fringe case where the function prints to screen AND requires two arguments -- the first arg being the element value and the second arg being the element key.

Because you want to only process the element values AND modify them by reference, &$v is necessary.

Here is a relevant post to read regarding checking the dynamic function name: What exactly is the difference between the is_callable and function_exists in PHP?

Here is my working alternative:

Code: (Demo)

$multidim_array = ['a' => [' \one ', ['b' => 'two\\', [['c' => 'thr\ee']]]]];
$func = 'stripslashes';
if (function_exists($func)) {
array_walk_recursive($multidim_array, function(&$v)use($func){$v = $func($v);});
var_export($multidim_array);
} else {
echo "not callable";
}

If you wanted to go down this rabbit hole further, you could extend its potential utility by setting up the option to pass multiple arguments:

Code: (Demo)

$func = 'strpos';
if (function_exists($func)) {
$more = true;
$param2 = 'o';
array_walk_recursive($multidim_array, function(&$v)use($func, $more, $param2) {
if ($more) {
$v = $func($v, $param2);
} else {
$v = $func($v);
}
});
var_export($multidim_array);
} else {
echo "um... I'm not calling $func";
}

Finally, the approach that I whole-heartedly do NOT endorse is the use of eval() -- because you can see the tail, horns, and pitchfork a mile away.

Caution
The eval() language construct is very dangerous because it allows execution of arbitrary PHP code. Its use thus is discouraged. If you have carefully verified that there is no other option than to use this construct, pay special attention not to pass any user provided data into it without properly validating it beforehand.

This works, but really should not be entertained:

if (function_exists($func)) {
array_walk_recursive($multidim_array, function(&$v)use($func) {eval("\$v = $func(\$v);"); });
var_export($multidim_array);
} else {
echo "um... I'm not calling $func";
}


Related Topics



Leave a reply



Submit