Вот, решил вступить в холивар по поводу скорости работы циклов. Аналитику проводил не блога ради — работы для. В конце поста мы, думаю, окончательно определимся с тем, какие циклы в каких случаях лучше применять.
Интернет пестрит тестами циклов всех времен и народов. При этом, результаты этих тестов всегда разные. У кого-то побеждает do...while
, у кого-то for
. Однажды я даже наткнулся на тест, в котором победителем вышел цикл вида foreach ($a as $b=>$c)
, и лидерство объяснялось как раз ключом $b
.
Но, кто прав? Я думаю, что мало кто из них. И, одновременно, все они. Это как? Да очень просто: большинство тестов посвящено однородному использованию циклов, хотя и сделаны они были для разных целей.
Давайте разберёмся. Для начала поработаем с массивом строк, а потом попробуем поработать математикой.
Итак, имеем файл, который содержит в себе 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 |
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
для индексированных массивов или оптимизировать код, если у вас именованный массив.