Ich habe letztens meine PHP-Library phpgeo mit Scrutinizer CI auf Code-Qualität untersuchen lassen. Es wurde an zwei Stellen Code-Duplication angemahnt, die ich dann auch gleich entfernt habe.
Dabei stieß ich auf eine foreach
-Schleife, welche für die Berechnung der Länge einer Polyline (GPS-Track) zuständig ist.
Das Ergebnis der Schleife lässt sich auch wunderbar mit PHPs Funktion array_reduce
berechnen. Die Frage war jetzt, ob ich es so lasse wie gehabt oder tatsächlich auf array_reduce
umstelle.
Die betreffende Methode:
public function getLength(DistanceInterface $calculator) { $distance = 0.0; if (count($this->points) <= 1) { return $distance; } foreach ($this->getSegments() as $segment) { $distance += $segment->getLength($calculator); } return $distance; }
lässt sich auch so schreiben:
public function getLength(DistanceInterface $calculator) { $distance = 0.0; if (count($this->points) <= 1) { return $distance; } return array_reduce( $this->getSegments(), function($carry, $item) use ($calculator) { return $carry + $item->getLength($calculator); } ); }
array_reduce
müsste schon mindestens einen relevanten Vorteil gegenüber foreach
bieten und außer der Geschwindigkeit und vielleicht ein bisschen mehr Eleganz fiel mir keiner ein. Die Geschwindigkeit lässt sich zum Glück schnell testen und ein kurzes Skript für diesen Zweck war schnell geschrieben:
<?php /** * we need something to call */ function calculate_something($value) { return $value / ($value + 1); } $cycles = 100000; // create some random data $array = []; for ($i = 0; $i < $cycles; $i ++) { $array[$i] = mt_rand(0, 0x7fffffff); } $timer['start'] = microtime(true); $sumForeach = 0; foreach ($array as $value) { $sumForeach += calculate_something($value); } $timer['foreach'] = microtime(true); $sumArrayReduce = array_reduce($array, function($carry, $item) { return $carry + calculate_something($item); }); $timer['array_reduce'] = microtime(true); echo $sumForeach . " == " . $sumArrayReduce . PHP_EOL; echo "foreach : " . ($timer['foreach'] - $timer['start']) . PHP_EOL; echo "array_reduce: " . ($timer['array_reduce'] - $timer['foreach']) . PHP_EOL;
Es zeigte sich, dass foreach
deutlich schneller ist als array_reduce
:
[mj@neptune:~/tmp] % php test.php 99999.999335167 == 99999.999335167 foreach : 0.068737030029297 array_reduce: 0.2411630153656
Wenn man darüber nachdenkt ist auch schnell klar warum das so sein muss: Für jeden Wert des Arrays wird in array_reduce
ein - in PHP relativ teurer - Funktionsaufruf fällig (nämlich die Callback-Closure), was in foreach
nicht notwendig ist.
Ende der Geschichte: Die Längenberechnung in phpgeo geschieht weiterhin in einer foreach
-Schleife.
Es ist allerdings leicht vorstellbar, dass der Overhead eines Funktionsaufrufes bei anderen Anwendungen weniger ins Gewicht fällt und dort array_reduce
die elegantere Alternative darstellen kann.