In my previous benchmark post about the splat operator the array_map function was very slow compared to foreach
. I've read a great deal that the SplFixedArray is fast and memory efficient over classic PHP array. I wanted to test it with the thought that I can't seem to get out of my head lately: how to create a smart collection in PHP.
I've created simple class with 2 map method implementation. mapMap
is using the array_map
function and the foreachMap
method is emulating the same functionality with foreach
.Here is the benchmark script:
<?php
class MapCollection
{
private $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function mapMap(callable $callable): self
{
return new self(array_map($callable, $this->items));
}
public function foreachMap(callable $callable): self
{
$changes = [];
foreach ($this->items as $key => $item) {
$changes[$key] = $callable($item);
}
return new self($changes);
}
}
class SplMapCollection extends \SplFixedArray
{
public function __construct(array $items)
{
parent::__construct(count($items));
foreach ($items as $key => $item) {
parent::offsetSet($key, $item);
}
}
public function mapMap(callable $callable): self
{
return new self(array_map($callable, $this->toArray()));
}
public function foreachMap(callable $callable): self
{
$changes = [];
foreach ($this->toArray() as $key => $item) {
$changes[$key] = $callable($item);
}
return new self($changes);
}
}
$input = [];
for ($i = 0; $i < 1000000; $i++) {
$input[] = $i;
}
$callable = function (int $item) {
return $item++;
};
$time = microtime(true);
$memory = memory_get_usage(true);
$mapCollection = new MapCollection($input);
writeResults('MapCollection::__construct', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$mapMap = $mapCollection->mapMap($callable);
writeResults('MapCollection::mapMap', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$mapMap = $mapCollection->foreachMap($callable);
writeResults('MapCollection::foreachMap', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$splMapCollection = new SplMapCollection($input);
writeResults('splMapCollection::__construct', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$mapMap = $splMapCollection->mapMap($callable);
writeResults('splMapCollection::mapMap', $time, $memory);
$time = microtime(true);
$memory = memory_get_usage(true);
$mapMap = $splMapCollection->foreachMap($callable);
writeResults('splMapCollection::foreachMap', $time, $memory);
function writeResults(string $class, float $time, float $memory): void
{
$timeUsage = microtime(true) - $time;
$memoryUsage = memory_get_usage(true) - $memory;
echo "{$class} processed in {$timeUsage} ms and used {$memoryUsage} \n\n";
}
And here are the results:
MapCollection::__construct processed in 0.00011277198791504 ms and used 0
MapCollection::mapMap processed in 1.0667450428009 ms and used 33558528
MapCollection::foreachMap processed in 1.4232130050659 ms and used 0
splMapCollection::__construct processed in 0.94543695449829 ms and used 16003072
splMapCollection::mapMap processed in 2.1207430362701 ms and used -17555456
splMapCollection::foreachMap processed in 2.3400418758392 ms and used 0
Conclusion
array_map vs foreach
array_map
was way slower in my splat operator post because it creates a new array. Foreach
was just modifying the existing array. If I emulate the same functionality with foreach
where a new array is created, foreach
is slightly slower. In the previous post I stated that I will think twice to use array_map
again. If it's used for the use case is was designed for then it's the best option.
Array vs SplFixedArray
I must be using it wrong. SplFixedArray is way slower compared to just Array. I'll stick with array until I find a good use case or learn how to use SplFixedArray properly.