What's the performance difference (if there is any) between these three approaches, both used to transform an array to another array?
- Using
foreach - Using
array_mapwith lambda/closure function - Using
array_mapwith 'static' function/method - Is there any other approach?
To make myself clear, let's have look at the examples, all doing the same - multiplying the array of numbers by 10:
$numbers = range(0, 1000);
Foreach
$result = array();
foreach ($numbers as $number) {
$result[] = $number * 10;
}
return $result;
Map with lambda
return array_map(function($number) {
return $number * 10;
}, $numbers);
Map with 'static' function, passed as string reference
function tenTimes($number) {
return $number * 10;
}
return array_map('tenTimes', $numbers);
Is there any other approach? I will be happy to hear actually all differences between the cases from above, and any inputs why one should be used instead of others.
-
12Why don't you just benchmark and see what happens?Jon– Jon2013年08月09日 10:38:30 +00:00Commented Aug 9, 2013 at 10:38
-
26Well, I may make a benchmark. But I still do not know how it internally works. Even if I find out one is faster, I still do not know why. Is it because of the PHP version? Does it depend on the data? Is there a difference between associative and ordinary arrays? Of course I can make whole suite of benchmarks but getting some theory saves here a lot of time. I hope you understand...Pavel S.– Pavel S.2013年08月09日 14:21:34 +00:00Commented Aug 9, 2013 at 14:21
-
3Late comment, but isn't while( list($k, $v)= each($array)) faster than all the above? I haven't benchmarked this in php5.6, but it was in earlier versions.Owen Beresford– Owen Beresford2015年07月01日 17:33:35 +00:00Commented Jul 1, 2015 at 17:33
6 Answers 6
Its interesting to run this benchmark with xdebug disabled, as xdebug adds quite a lot of overhead, esp to function calls.
This is FGM's script run using 5.6 With xdebug
ForEach : 0.79232501983643
MapClosure: 4.1082420349121
MapNamed : 1.7884571552277
Without xdebug
ForEach : 0.69830799102783
MapClosure: 0.78584599494934
MapNamed : 0.85125398635864
Here there is only a very small difference between the foreach and closure version.
Its also interesting to add a version with a closure with a use
function useMapClosureI($numbers) {
$i = 10;
return array_map(function($number) use ($i) {
return $number * $i++;
}, $numbers);
}
For comparison I add:
function useForEachI($numbers) {
$result = array();
$i = 10;
foreach ($numbers as $number) {
$result[] = $number * $i++;
}
return $result;
}
Here we can see it makes an impact on the closure version, whereas the array hasn't noticeably changed.
19/11/2015 I have also now added results using PHP 7 and HHVM for comparison. The conclusions are similar, though everything is much faster.
PHP 5.6
ForEach : 0.57499806880951
MapClosure : 0.59327731132507
MapNamed : 0.69694859981537
MapClosureI: 0.73265469074249
ForEachI : 0.60068697929382
PHP 7
ForEach : 0.11297199726105
MapClosure : 0.16404168605804
MapNamed : 0.11067249774933
MapClosureI: 0.19481580257416
ForEachI : 0.10989861488342
HHVM
ForEach : 0.090071058273315
MapClosure : 0.10432276725769
MapNamed : 0.1091267824173
MapClosureI: 0.11197068691254
ForEachI : 0.092114186286926
6 Comments
array_map (and its related functions array_reduce, array_filter) let you write beautiful code. If array_map was much slower it would be a reason to use foreach, but its very similar, so I will use array_map everywhere it makes sense.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
10 Comments
useMapNamed is actually faster than useArray. Thought that was worth mentioning.lap, don't you want the range() call above the first microtime call? (Though probably insignificant compared with the time for the loop.)Here are some updated tests for the current PHP 8 (RC2) version. Also added short closures
PHP 8.0 RC2
Foreach: 0.093745978673299
MapClosure: 0.096948345502218
MapShortClosure: 0.096264243125916
MapNamed: 0.091399153073629
MapClosureI: 0.11352666219076
ForEachI: 0.097501540184021
1 Comment
It's interesting. But I've got an opposite result with the following codes which are simplified from my current projects:
// test a simple array_map in the real world.
function test_array_map($data){
return array_map(function($row){
return array(
'productId' => $row['id'] + 1,
'productName' => $row['name'],
'desc' => $row['remark']
);
}, $data);
}
// Another with local variable $i
function test_array_map_use_local($data){
$i = 0;
return array_map(function($row) use ($i) {
$i++;
return array(
'productId' => $row['id'] + $i,
'productName' => $row['name'],
'desc' => $row['remark']
);
}, $data);
}
// test a simple foreach in the real world
function test_foreach($data){
$result = array();
foreach ($data as $row) {
$tmp = array();
$tmp['productId'] = $row['id'] + 1;
$tmp['productName'] = $row['name'];
$tmp['desc'] = $row['remark'];
$result[] = $tmp;
}
return $result;
}
// Another with local variable $i
function test_foreach_use_local($data){
$result = array();
$i = 0;
foreach ($data as $row) {
$i++;
$tmp = array();
$tmp['productId'] = $row['id'] + $i;
$tmp['productName'] = $row['name'];
$tmp['desc'] = $row['remark'];
$result[] = $tmp;
}
return $result;
}
Here is my testing data and codes:
$data = array_fill(0, 10000, array(
'id' => 1,
'name' => 'test',
'remark' => 'ok'
));
$tests = array(
'array_map' => array(),
'foreach' => array(),
'array_map_use_local' => array(),
'foreach_use_local' => array(),
);
for ($i = 0; $i < 100; $i++){
foreach ($tests as $testName => &$records) {
$start = microtime(true);
call_user_func("test_$testName", $data);
$delta = microtime(true) - $start;
$records[] = $delta;
}
}
// output result:
foreach ($tests as $name => &$records) {
printf('%.4f : %s '.PHP_EOL,
array_sum($records) / count($records), $name);
}
The result is:
0.0098 : array_map 0.0114 : foreach 0.0114 : array_map_use_local 0.0115 : foreach_use_local
My tests were in LAMP production environment without xdebug. I'am wandering xdebug would slow down array_map's performance.
3 Comments
array_map ;)array_map and foreach using Xhprof. And Its interesting array_map consumes more memory than ` foreach`.foreach loops you have 3 more assignments and temporary variable creation. It's no surprise to me that's slower than one array assignment.$ php -v
PHP 8.3.11 (cli) (built: Aug 27 2024 19:16:34) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.3.11, Copyright (c) Zend Technologies
with the ionCube PHP Loader v13.3.1, Copyright (c) 2002-2024, by ionCube Ltd.
with Zend OPcache v8.3.11, Copyright (c), by Zend Technologies
array_map execution time: 0.040987968444824 seconds
foreach execution time: 0.051311016082764 seconds
array_map execution time: 0.04297399520874 seconds
foreach execution time: 0.052814960479736 seconds
Both methods produce the same result.
array (
0 => 2,
1 => 4,
2 => 6,
3 => 8,
4 => 10,
5 => 12,
6 => 14,
7 => 16,
8 => 18,
9 => 20,
// Sample data array
$array = range(1, 1000000); // Array with 1M elements
// Operation: A simple operation that will be applied to each element
function operation($n)
{
return $n * 2;
}
// Measure execution time for array_map
$startTime = microtime(true);
$result_map = array_map('operation', $array);
$endTime = microtime(true);
$map_time = $endTime - $startTime;
echo 'array_map execution time: '.$map_time." seconds\n";
// Measure execution time for foreach loop
$startTime = microtime(true);
$result_foreach = [];
foreach ($array as $n) {
$result_foreach[] = operation($n);
}
$endTime = microtime(true);
$foreach_time = $endTime - $startTime;
echo 'foreach execution time: '.$foreach_time." seconds\n";
// Measure execution time for array_map
$startTime = microtime(true);
$result_map = array_map('operation', $array);
$endTime = microtime(true);
$map_time = $endTime - $startTime;
echo 'array_map execution time: '.$map_time." seconds\n";
// Measure execution time for foreach loop
$startTime = microtime(true);
$result_foreach = [];
foreach ($array as $n) {
$result_foreach[] = operation($n);
}
$endTime = microtime(true);
$foreach_time = $endTime - $startTime;
echo 'foreach execution time: '.$foreach_time." seconds\n";
// Compare results
if ($result_map === $result_foreach) {
echo "Both methods produce the same result.\n";
} else {
echo "Results differ between array_map and foreach.\n";
}
var_export(array_slice($result_map, 0, 10));
Comments
I tried testing @FGM's code on PHP 8 and window 10 in 10 times. And this is result: Image
I don't know if PHP could have JIT. I guess it had JIT in PHP8 because in file php.ini, I saw 1 config command in php.ini: auto_globals_jit=On.
1 Comment
Explore related questions
See similar questions with these tags.