Дек 272011
 

Циклы: for, foreach, while, count/sizeof() — ускорение 15%-30%

В начале программы создается массив $test из целых чисел (100 000 элементов). Потом один раз запускаются приведенные ниже примеры. Цикл проходит данный массив 3-мя способами (разными циклами) и выполняет кое-какие операции. Не выполнять в цикле ничего нельзя, ибо это будет уже совсем не реальный тест.

  1. {$x=0; foreach($test as $n)                          { $x=sprintf("test%08i",$i);        }}
  2. {$x=0; for ($it=0; $it<100000; $it++)                { $x=sprintf("test%08i",$i);        }}
  3. {$x=0; $it=0; while($it<100000)                      { $x=sprintf("test%08i",$i); $it++; }}
  4. {$x=0; for ($it=0; $it<count($test); $it++)          { $x=sprintf("test%08i",$i);        }}
  5. {$x=0; $it=0; while($it<count($test))                { $x=sprintf("test%08i",$i); $it++; }}
  6. {$x=0; $co=count($test); for ($it=0; $it<$co; $it++) { $x=sprintf("test%08i",$i);        }}
  7. {$x=0; $co=count($test); $it=0; while($it<$co)       { $x=sprintf("test%08i",$i); $it++; }}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 12.0313 12.0313 154.4% 100.0%
test N2 1 4.7290 4.7290 00.0% 39.3%
test N3 1 4.7712 4.7712 00.9% 39.7%
test N4 1 10.2847 10.2847 117.5% 85.5%
test N5 1 10.3466 10.3466 118.8% 86.0%
test N6 1 9.1271 9.1271 93.0% 75.9%
test N7 1 9.1409 9.1409 93.3% 76.0%

Почему sprintf, а не реальное echoecho использовать нельзя, т.к. от него будет немерянный буфер (OUTPUT в браузер или консоль).

Теперь о деле. Бесспорный вывод — использование foreach сильно тормозит дело, а между for и while большой разницы нет. (На голом тесте for/while/foreach {..}тормоза foreach — 30%). Это не удивительно, т.к. foreach делает копию массива, на что тратиться масса времени (хотя это только слухи).

Вывод с count() не столь очевиден, потому что от разного текста в цикле % тормознутости от самого быстрого варианта резко возрастает… Я взял цикл с небольшой нагрузкой — проход по огромному массиву $test + форматирование функцией sprintf. Как видите, варинты с count() и заменяющей эту функцию перемнной $co различаются на 10% по скорости между собой (не смотрите на варинант с константой в 100000, заранее знать кол-во элементов невозможно).

Вывод о не ассоциативных массивах: 1) foreach существенно замедляет работу 2) использование count() в простых циклах — замедленение 10%. Но на сложных циклах потери от лишних запусков count() будут абсолютно незаметны, так что ситуация не очевидна.

Сравнение count() и sizeof().

Судя по мануалу — это алиасы. Об этом написано на страницах самих функций и дополнительной странице «Appendex => Aliases list». Что же мы видим на массиве в 100000 элементов:

  1. {$x=0; for ($it=0; $it<count($test); $it++)  { $x=sprintf("test%08i",$test[$it]);}}
  2. {$x=0; for ($it=0; $it<sizeof($test); $it++) { $x=sprintf("test%08i",$test[$it]);}}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 3.0087 3.0087 15.7% 100.0%
test N2 1 2.5998 2.5998 00.0% 86.4%

Пусть тесты будут иметь погрешности… Но результат один — count() заметно отстает по скорости от sizeof()! Хм, я бы к записи в мануале сделал приписку: «The sizeof() function is an alias for count(), but последний сильно тормозит!»

Если кол-во элементов в массиве меньше 65000 (64К), то эти функции по скорости практически не различимы. Тут вывод простой — переходим на использованиеsizeof(), как ускоренного алиаса count(). Это принесет свои результаты на огромных массивах.

Ассоциативные массивы: тестирование разных способов перебора

С ними наблюдается таже проблема: на разных по величине массивах разные функции эффективны, но лучше всех foreach!

Массив в 200 элементов и 1000 повторов программы:

  1. {$x=0; foreach($test as $k=>$v) { $x=sprintf("%s=>%s\n",$k,$v);                                                           }}
  2. {$x=0; reset($test); while (list($k, $v) = each($test)) { $x=sprintf("%s=>%s\n",$k,$v);                                   }}
  3. {$x=0; $k=array_keys($test); $co=sizeof($k); for ($it=0; $it<$co; $it++) { $x=sprintf("%s=>%s\n",$k[$it],$test[$k[$it]]); }}
  4. {$x=0; reset($test); while ($k=key($test)) { $x=sprintf("%s=>%s\n",$k,current($test)); next($test);                       }}

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 8.1222 8.1222 00.0% 78.7%
test N2 1 10.3221 10.3221 27.1% 100.0%
test N3 1 9.7921 9.7921 20.6% 94.9%
test N4 1 8.9711 8.9711 10.5% 86.9%

Тоже самое, но массив в 5000 элементов и 200 повторов:

 

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 14.4473 14.4473 00.0% 67.2%
test N2 1 18.6801 18.6801 29.3% 86.9%
test N3 1 21.5056 21.5056 48.9% 100.0%
test N4 1 15.8514 15.8514 09.7% 73.7%

Опять тоже самое, но массив в 100 000 элементов и без повторов:

 

 

счетчик кол-во
вызовов
общее
вpемя
сpеднее
вpемя
% от min % от max
test N1 1 3.5116 3.5116 00.0% 82.8%
test N2 1 3.9724 3.9724 13.1% 93.6%
test N3 1 4.2436 4.2436 20.8% 100.0%
test N4 1 4.0026 4.0026 14.0% 94.3%

Другие тесты на холостых циклах тоже показывают преимущество foreach.

Резюме:

  • sizeof() лучше, чем count()
  • в циклах sizeof лучше вообще заменить на переменную
  • for и while практически не отличимы
  • для перебора простых индексных массивов нужно использовать for или while
  • для перебора ассоциативных массивов нужно использотьва foreach