array_* vs foreach или PHP7 vs PHP5

Добрых суток. Как-то за кадром остался вопрос прироста производительности стандартных функций 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

Понятно, что цифры относительны. Но некоторые выводы, уважаемые коллеги, сделать позволяют.
  • +11
  • 9,1k
  • 9
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 9
  • +2
    Я думаю, что код не совсем верен. Замените в случае с array_ функциями $data на $newData, Вы увидите совершенно другие результаты.
    • –12
      image
      время сливать карму
      • +1
        Очень жаль, что подобные вам выбрали хабр для слива собственной кармы.
      • +2
        Но некоторые выводы, уважаемые коллеги, сделать позволяют.

        например что делать подобным образом подобные микро-бенчмарки не очень-то хорошая идея. Есть замечательный инструмент для подобного: 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

        А вот другой аспект использования array_* функций, такой как возможность выстроить тело цикла как композицию функций, этот момент как раз таки и важен.

        • 0
          В наше время версии PHP 5 и 7 — это ниочём. Как минимум 4 версии надо рассматривать сегодня: 5.6, 7.0, 7.1 и 7.2.
          • 0
            И уж точно в результатах указать конкретные, хотя бы минорные.
          • +1
            Набор данных явно можно взять побольше.
            • +1

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

              • +4

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

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