Добрых суток. Как-то за кадром остался вопрос прироста производительности стандартных функций PHP при работе с массивами в версии 7.*.

Статьи (например тут, тут), освещавшие этот вопрос о более ранних версиях, говорили, что обычные циклы с точки зрения производительности более выгодны.

А вот здесь можно уже увидеть тесты, для 7-ой версии. И то не все так в ней однозначно. В тестах от 19/11/2015 циклы и встроенные функции сравнялись по производительности, и только последний топик наводит нас на размышления.

А что же по итогу… Я решил все проверить самостоятельно и прогнать несколько тестов…

1) array_filter

исходный код
$data = range(0, 10000);
$start = microtime(true);
$data = array_filter($data, function ($item) {
    return $item%2;
});
$end = microtime(true);
 
echo $end - $start.'  ';

 $data = range(0, 10000);
$start = microtime(true);
$newData = array();
foreach ($data as $item) {
    if ($item%2) {
        $newData[] = $item;
    }
}
$end = microtime(true);
 
echo $end - $start.'  ';

 $data = range(0, 10000);
$start = microtime(true);
$newData = array();
$numItems = count($data);
for($i=1;$i<=$numItems-1;$i++) {
    if ($data[$i]%2) {
        $newData[] = $data[$i];
    }
}
$end = microtime(true);
 
echo $end - $start.'  ';


 $data = range(0, 10000);
$start = microtime(true);
$newData = array();
$numItems = count($data);
$i = 0;
while ($i <= $numItems-1) {
    if ($data[$i]%2) {
        $newData[] = $data[$i];
    }
    $i++;
}
$end = microtime(true);
 
echo $end - $start.'  ';


Округленно…
функция PHP5 PHP7
array_filter 0.00282 0.00136
foreach 0.0013 0.00045
for 0.00171 0.00072
while 0.00145 0.00054

2) array_map

исходный код

<?php
$data = range(0, 10000);

$start = microtime(true);
$data = array_map(function ($item) {
    return $item+1;
}, $data);
$end = microtime(true);
 
echo $end - $start;
 
$data = range(0, 10000);
$start = microtime(true);
$newData = array();
foreach ($data as $item) {
    $newData[] = $item+1;
}
$end = microtime(true);
 
echo $end - $start;
 
$data = range(0, 10000);
$start = microtime(true);
$newData = array();
$numItems = count($data);
for($i=0;$i<$numItems;$i++) {
    $newData[] = $data[$i]+1;
}
$end = microtime(true);
 
echo $end - $start;
 
$data = range(0, 10000);
$start = microtime(true);
$newData = array();
$numItems = count($data);
$i = 0;
while ($i < $numItems) {
    $newData[] = $data[$i];
    $i++;
}
$end = microtime(true);
 
echo $end - $start;


функция PHP5 PHP7
array_map 0.00462 0.00094
foreach 0.00155 0.00033
for 0.00220 0.00044
while 0.00169 0.00054

3) array_walk

исходный код

$data = range(0, 10000);

$start = microtime(true);
$data = array_walk($data, function ($item) {
    return $item+1;
});
$end = microtime(true);
 
echo $end - $start;
 $data = range(0, 10000);
$start = microtime(true);
$newData = array();
foreach ($data as $item) {
    $newData[] = $item+1;
}
$end = microtime(true);
 
echo $end - $start;
 $data = range(0, 10000);
$start = microtime(true);
$newData = array();
$numItems = count($data);
for($i=0;$i<$numItems;$i++) {
    $newData[] = $data[$i]+1;
}
$end = microtime(true);
 
echo $end - $start;
 $data = range(0, 10000);
$start = microtime(true);
$newData = array();
$numItems = count($data);
$i = 0;
while ($i < $numItems) {
    $newData[] = $data[$i];
    $i++;
}
$end = microtime(true);
 
echo $end - $start;


функция PHP5 PHP7
array_walk 0.00285 0.00101
foreach 0.00290 0.00088
for 0.00219 0.00043
while 0.00173 0.00086

4) array_reduce

исходный код

$data = range(0, 10000);

$start = microtime(true);
$data = array_reduce($data, function ($carry, $item) {
    $carry += $item;
    return $carry;
},0);
$end = microtime(true);
echo $end - $start;

 $data = range(0, 10000);
$start = microtime(true);
$newData = 0;
foreach ($data as $item) {
    $newData+= $item;
}
$end = microtime(true);

echo $end - $start;
 $data = range(0, 10000);
$start = microtime(true);
$newData = 0;
$numItems = count($data);
for($i=0;$i<$numItems;$i++) {
    $newData+= $data[$i];
}
$end = microtime(true);


echo $end - $start;
 $data = range(0, 10000);
$start = microtime(true);
$newData = 0;
$numItems = count($data);
$i = 0;
while ($i < $numItems) {
    $newData+= $data[$i];
    $i++;
}
$end = microtime(true);
echo $end - $start;


функция PHP5 PHP7
array_reduce 0.00239 0.00092
foreach 0.00044 0.00020
for 0.00066 0.00029
while 0.00062 0.00029

Понятно, что цифры относительны. Но некоторые выводы, уважаемые коллеги, сделать позволяют.

Комментарии (9)


  1. Nikolay_Smeh
    22.10.2017 21:19

    Я думаю, что код не совсем верен. Замените в случае с array_ функциями $data на $newData, Вы увидите совершенно другие результаты.


  1. xakepmega
    22.10.2017 21:24

    image
    время сливать карму


    1. ilyaplot
      23.10.2017 12:11

      Очень жаль, что подобные вам выбрали хабр для слива собственной кармы.


  1. Fesor
    22.10.2017 21:28

    Но некоторые выводы, уважаемые коллеги, сделать позволяют.

    например что делать подобным образом подобные микро-бенчмарки не очень-то хорошая идея. Есть замечательный инструмент для подобного: phpbench. Как минимум с точки зрения статистической погрешности будет чище.


    Далее, какие выводы еще мы можем сделать… Да собственно никаких. Методика проведения измерений мягко скажем не дает вообще никаких данных о том какие выводы мы можем сделать. Да, циклы будут всегда быстрее (особенно foreach) чем array_* функции тупо за счет отсутствия необходимости делать вызов функции на каждую итерацию. Причем что то что то — константа и на объемах выборок менее скажем десятков миллионов нам в целом плевать какой из вариантов мы используем.


    Позвольте привести мою версию вашего бенчмарка (мне лень потому возьму только array_filter:


    Результаты

    Исходник


    <?php
    
    /**
     * @BeforeMethods({"init"})
     */
    class ArrayFilterBenchmark
    {
        public function init()
        {
            $this->data = range(1, 100000);
        }
    
        public function benchArrayFilter()
        {
            array_filter($this->data, function ($item) {
                return $item % 2;
            });
        }
    
        public function benchForeach()
        {
            $data = $this->data;
            $newData = array();
            foreach ($data as $item) {
                if ($item % 2) {
                     $newData[] = $item;
                }
            }
        }
    }

    Результаты


    PhpBench 0.13.0. Running benchmarks.
    
    \ArrayFilterBenchmark
    
        benchArrayFilter              I99 P0    [? Mo]/r: 7,321.920 7,036.505 (?s)  [?SD ?RSD]/r: 576.149?s 7.87%
        benchForeach                  I99 P0    [? Mo]/r: 4,389.150 4,198.309 (?s)  [?SD ?RSD]/r: 463.250?s 10.55%
    
    2 subjects, 200 iterations, 2 revs, 0 rejects
    (best [mean mode] worst) = 3,947.000 [5,855.535 5,617.407] 6,370.000 (?s)
    ?T: 1,171,107.000?s ?SD/r 519.700?s ?RSD/r: 9.212%

    Запускалось на MacBook Pro 15" 2017, без XDebug.


    PHP 7.1.4 (cli) (built: May  6 2017 10:02:00) ( NTS )
    Copyright (c) 1997-2017 The PHP Group
    Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
        with Zend OPcache v7.1.4, Copyright (c) 1999-2017, by Zend Technologies


  1. VolCh
    22.10.2017 21:53

    В наше время версии PHP 5 и 7 — это ниочём. Как минимум 4 версии надо рассматривать сегодня: 5.6, 7.0, 7.1 и 7.2.


    1. VolCh
      22.10.2017 21:55

      И уж точно в результатах указать конкретные, хотя бы минорные.


  1. afgm
    22.10.2017 22:37

    Набор данных явно можно взять побольше.


  1. Gemorroj
    23.10.2017 00:45

    проведите подобные тесты с помощью специализированного инструмента.
    в ваших тестах, как минимум, ничего не сказано про потребляемую память и нет вариантов с разными данными в массивах.


  1. ReinRaus
    23.10.2017 12:21

    Есть подозрение, что Вы измеряли время, которое тратится на 10000 вызовов функции и возврат результата из этой функции. Попробуйте применить более «тяжелые» вычисления и это время станет незаметно в общем результате.