Сравнение скорости работы циклов в PHP

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

Интернет пестрит тестами циклов всех времен и народов. При этом, результаты этих тестов всегда разные. У кого-то побеждает do...while, у кого-то for. Однажды я даже наткнулся на тест, в котором победителем вышел цикл вида foreach ($a as $b=>$c), и лидерство объяснялось как раз ключом $b.

wat

Но, кто прав? Я думаю, что мало кто из них. И, одновременно, все они. Это как? Да очень просто: большинство тестов посвящено однородному использованию циклов, хотя и сделаны они были для разных целей.

Давайте разберёмся. Для начала поработаем с массивом строк, а потом попробуем поработать математикой.
Итак, имеем файл, который содержит в себе 729 487 строк и 61 118 856 символов. Пишем скриптик для перебора этого файла:

$ar = file('esenin.txt'); //Массив строк
$car = count($ar); //Размер массива

$w = 0; //Результат для while
$dw = 0; //Результат для do-while
$f = 0; //Результат для for
$fe = 0; //Результат для foreach ($a as $b)
$fe2 = 0; //Результат для foreach ($a as $b=>$c)

echo "Строк: $car <br>";

$start0 = microtime(true);//засекаем время старта
$a = 0;//задаём индекс

while($b = $ar[$a]) {
    $w += strlen($b);
    $a++;
}
$res_w = microtime(true) - $start0;//Результат
echo "while был выполнен за $res_w сек, результат расчета: $w<br>";

$start1 = microtime(true);//засекаем время старта
$a = 0;//задаём индекс
do{
    $dw += strlen($ar[$a]);
}
while(++$a < $car);
$res_dw = microtime(true) - $start1;//Результат
echo "do while был выполнен за $res_dw сек, результат расчета: $dw<br>";


$start2 = microtime(true);
for($i = 0; $i < $car; $i++) {
    $f += strlen($ar[$i]);
}
$res_f = microtime(true) - $start2;//Результат
echo "for был выполнен за $res_f сек, результат расчета: $f<br>";

$start3 = microtime(true);
foreach($ar as $b) {
    $fe += strlen($b);
}
$res_fe = microtime(true) - $start3;//Результат
echo "foreach \$c as \$b был выполнен за $res_fe сек, результат расчета: $fe<br>";

$start4 = microtime(true);
foreach($ar as $a => $b) {
    $fe2 += strlen($b);
}
$res_fe2 = microtime(true) - $start4;//Результат
echo "foreach \$c as \$a => \$b был выполнен за $res_fe2 сек, результат расчета: $fe2<br>";

Исполняем его на разных версиях PHP

Результаты для PHP 5.6

while do-while for foreach
$c as $b
foreach
$c as $a => $b
0,09449 0,08536 0,09223 0,07864 0,08539
0,09100 0,10605 0,10352 0,08532 0,09208
0,09191 0,08581 0,08986 0,07740 0,08423
0,08946 0,08275 0,08967 0,07783 0,08473
0,09063 0,08370 0,08910 0,08022 0,08513
0,09197 0,08484 0,09254 0,07754 0,08829
0,09226 0,08438 0,09194 0,07860 0,08476
Усреднённые значения
0,09167 0,08756 0,09269 0,07936 0,08637
Получается, что на PHP 5.6 в лидерах по количеству фейлов в переборе массива у нас while, а по скорости исполнения, получается, первое место у foreach, второе у его брата. Самым медленным здесь оказался цикл for. Справедливости ради, стоит отметить, что for зафейлился только благодаря второму исполнению скрипта. Если убрать вторую строчку из таблицы, то антилидером станет while.

Однако, из песни слова не выкинешь, как говорится, поэтому корректировать статистику я не буду. Просто оставлю это здесь, а выводы делайте сами. Давайте лучше посмотрим что нам покажет PHP 7.

Результаты для PHP 7.1

while do-while for foreach
$c as $b
foreach
$c as $a => $b
0,03143 0,02442 0,02401 0,02343 0,02514
0,02826 0,02414 0,02374 0,02333 0,02523
0,03261 0,02451 0,02392 0,02331 0,02488
0,03053 0,02548 0,02508 0,02425 0,02616
0,02756 0,02318 0,02242 0,02110 0,02313
0,02805 0,02391 0,02332 0,02267 0,02428
0,02859 0,02440 0,02311 0,02186 0,02439
Усреднённые значения
0,02957 0,02429 0,02366 0,02285 0,02474

Здесь также видно, что быстрее всего перебирается массив при помощи foreach без обозначения индекса, на втором месте идёт for, а полный фейл у while.

Да, здесь не было неудачной загрузки скрипта и for, всё-таки восстановил своё доброе имя. Однако, даже ребёнок увидит, что результаты стали более стабильными: while теперь самый медленный всегда, а for же выбился на стабильное второе место. Ну, а пальма лидерства, бурные овации и фонтан шампанского у foreach.

Но теперь давайте перейдём к математике. В силу особенностей, предыдущего лидера нам прийдётся исключить, поскольку массивы мы перебирать не убдем. Вместо этого попробуем поискать количество ответов на все вопросы в факториале 11.

Итак, поехали:

function FactoryWhile()
{
    global $n;
    $result = 1;

    $i=1;
    while($i <= $n) {
        $result *= $i;
        $i++;
    }

    return $result;
}

function FortyTwoWhile($n) {
    $i = 0;
    $c = 0;

    while($i <= $n) {
        if($i%42 == 0)
            $c++;
        $i++;
    }

    return $c;
}

function FactoryDoWhile()
{
    global $n;
    $result = 1;

    $i=1;
    do {
        $result *= $i;
    }
    while(++$i <= $n);

    return $result;
}

function FortyTwoDoWhile($n) {
    $i = 0;
    $c = 0;

    do {
        if($i%42 == 0)
            $c++;
    }
    while(++$i <= $n);

    return $c;
}

function FactoryFor()
{
    global $n;
    $result = 1;

    for($i=1; $i <= $n; $i++)
    {
        $result *= $i;
    }

    return $result;
}

function FortyTwoFor($n) {

    $c = 0;

    for($i = 0;$i <= $n; $i++ ) {
        if($i%42 == 0)
            $c++;
        $i++;
    }

    return $c;
}

$n = 11;//Требуемое число

echo "Сколько раз встречается число кратное 42 в факториале $n <br>";

$start0 = microtime(true);//засекаем время старта

$a_0 = FactoryWhile();
$res_0 = FortyTwoWhile($a_0);

$res_w = microtime(true) - $start0;//Результат
echo "while был выполнен за $res_w сек, результат: $res_0 <br>";

$start1 = microtime(true);//засекаем время старта

$a_1 = FactoryDoWhile();
$res_1 = FortyTwoDoWhile($a_1);

$res_dw = microtime(true) - $start1;//Результат
echo "do-while был выполнен за $res_dw сек, результат: $res_1 <br>";

$start2 = microtime(true);//засекаем время старта

$a_2 = FactoryFor();
$res_2 = FortyTwoFor($a_2);

$res_f = microtime(true) - $start2;//Результат
echo "for был выполнен за $res_f сек, результат: $res_2 <br>";

Результаты для PHP 5.6

while do-while for
1,69724 1,52220 1,12401
1,71536 1,49457 1,09595
1,72356 1,58490 1,15494
1,74617 1,49063 1,13151
1,70951 1,51856 1,13674
1,72387 1,53812 1,13624
1,61443 1,51916 1,14721
Усреднённые значения
1,70431 1,52402 1,13237

Итак, наш while снова весь красный, а for отработал в полтора раза быстрее. Хвалёный do...while здесь — ни рыба, ни мясо. Может, после обновления он проявит себя?

Результаты для PHP 7.1

while do-while for
0,83518 0,84124 0,43251
0,83585 0,85702 0,4495
0,83488 0,85503 0,44945
0,85994 0,82302 0,41674
0,84059 0,85611 0,43957
0,78868 0,83990 0,46068
0,82926 0,80853 0,43280
Усреднённые значения
0,83205 0,84012 0,44018

К сожалению, нет. Прости, do...while, хоть ты и старался, как мог, но мы отложим тебя для других целей. А, вот, for будем использовать активнее, поскольку он быстрее, чем while почти в два раза.

Заключение

Давайте же расставим точки над ё.

while — лучше всего использовать, если у вас в наличии нет индекса. Например, при разборе объектов.
do...while — надстройку следует использовать, когда нужно выполнить итерацию, хотя бы один раз.
for — отлично помогает при расчетах, но проигрывает в скорости при переборе массива.
foreach ($a as $b) — смело перебираем им как индексированные, так и именованные массивы, не забывая, однако, что в этой конструкции создаётся копия массива, что сказывается на памяти.
foreach ($a as $b=>$c) — используем, если нужен ключ и не боимся за память. В противном случае, лучше использовать for для индексированных массивов или оптимизировать код, если у вас именованный массив.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.