Donβt be a language slave
· 3 min read
Last week I had an intriguing discussion with some Python guru (he got a nickname β reticulatus β a long ago.) I complained to him that the last year I was oblidged to code in PHP and now I am anxious about my IQ being decreased significally. That language virtually forces one to write an enormously nasty code. On the other hand, I finally feigned that PHP code might be written in functional manner as well. My friend archly grinned and suggested me to run benchmarks on functional-ity.
Well, I did.
The result is embarassing. I canβt even surmise how much scorn is to be felt to produce the interpreter that acts in the following way. Letβs take a look at the benchmarks. NB I wrote
the tiny wrapper
for calls to
microtime
. It does nothing but calculates times and memory usage between subsequent calls. Consider it a handy transparent utility.
$count = 1000000; // an amount of iterations
// test input, it will be mapped and reduced
$array = array();
for ($i = 0; $i < $count; $i++) {
$array[] = rand(1, $count);
}
// the wrapper to benchmark easily
$ys = new \Mudasobwa\Screwdrivers\YardStick(true);
// Mapping
$ys->milestone('MAP#Start');
$countew_array1 = array();
foreach ($array as $a) { $countew_array1[] = $a / 2; }
$ys->milestone('MAP#FOREACH');
$countew_array2 = array();
for ($i = 0; $i < $count; $i++) { $countew_array2[] = $array[$i] / 2; }
$ys->milestone('MAP#FOR');
$countew_array3 = array_map(function($a) { return $a / 2; }, $array);
$ys->milestone('MAP#ARRAY_MAP');
$ys->report('MAP.+'); // report measures for milestones `MAP*`
// Reducing
$ys->milestone('REDUCE#Start');
$average1 = 0;
foreach ($array as $a) { $average1 += $a / $count; }
$ys->milestone('REDUCE#FOREACH');
$average2 = 0;
for ($i = 0; $i < $count; $i++) { $average2 += $array[$i] / $count; }
$ys->milestone('REDUCE#FOR');
$average3 = array_reduce($array, function($memo, $a) use($count) {
return $memo + $a / $count;
}, 0);
$ys->milestone('REDUCE#ARRAY_REDUCE');
$ys->report('REDUCE.+'); // report measures for milestones `REDUCE*`
The results are shown below:
==== Diff for tags: [MAP#Start :: MAP#FOREACH]
-- βΒ Time: β 0.223850 sec
-- β Memory: β 141,009.3 KB
==== Diff for tags: [MAP#FOREACH :: MAP#FOR]
-- βΒ Time: β 0.240624 sec
-- β Memory: β 141,009.2 KB
==== Diff for tags: [MAP#FOR :: MAP#ARRAY_MAP]
-- βΒ Time: β 0.374310 sec
-- β Memory: β 141,009.2 KB
ββββββββββββββββββββββββββββββββββββββ
==== Diff for tags: [REDUCE#Start :: REDUCE#FOREACH]
-- βΒ Time: β 0.072303 sec
-- β Memory: β 4.6 KB
==== Diff for tags: [REDUCE#FOREACH :: REDUCE#FOR]
-- βΒ Time: β 0.086437 sec
-- β Memory: β 4.6 KB
==== Diff for tags: [REDUCE#FOR :: REDUCE#ARRAY_REDUCE]
-- βΒ Time: β 0.276434 sec
-- β Memory: β 4.6 KB
Memory consumption is the same, but
array_map
execution time is almost twice as much as
foreach
, and
array_reduce
is almost four times slower! WUT? Letβs turn back to ruby (Iβm not as reptile as my friend, I like jewels more than snakes.)
require 'benchmark'
n = 1_000_000
prng = Random.new
array = 1.upto(n).map { |_| 1.0 * (prng.rand n) }
array_new1 = []
array_new2 = []
array_new3 = []
Benchmark.bm do |x|
x.report { for i in 1...n; array_new1 << array[i] / 2; end }
x.report { array.each { |a| array_new2 << a / 2 } }
x.report { array_new3 = array.map { |a| a / 2 } }
end
reduce1 = reduce2 = reduce3 = 0
Benchmark.bm do |x|
x.report { for i in 1...n; reduce1 += array[i] / n; end }
x.report { array.each { |a| reduce2 += a / n } }
x.report { reduce3 = array.reduce(&:+) / n }
end
user system total real
0.130000 0.000000 0.130000 ( 0.130952)
0.110000 0.000000 0.110000 ( 0.104156)
0.090000 0.010000 0.100000 ( 0.097225)
user system total real
0.120000 0.000000 0.120000 ( 0.115846)
0.150000 0.000000 0.150000 ( 0.155771)
0.090000 0.000000 0.090000 ( 0.090643)
Ooh! Here we got what we expected from the modern language: mapping and reducing might be optimized comparing to dumb iteration and they occasionally were optimized. Both
map
and
reduce
are predictably
faster
than iterations. QED.
The difference in ruby is not significant, but the language is likely nudging us to use functional approach. Besides this code is a way more readable, it is just faster . While PHP... Well, thanks God my PHP diving season is over.