Производительность FOR vs FOREACH в PHP

Прежде всего, я понимаю, что в 90% приложений разница в производительности совершенно не имеет значения, но мне просто нужно знать, какая из них быстрее. Это и …

Информация, доступная в настоящее время в сети, путается. Многие люди говорят, что foreach – это плохо, но технически это должно быть быстрее, поскольку предполагается упростить запись обхода массива с использованием итераторов. Итераторы, которые, опять же, предполагают, что они быстрее, но в PHP также, по-видимому, мертвы медленно (или это не проблема PHP?). Я говорю о функциях массива: next () prev () reset () и т. Д. Ну, если они четные функции, а не одна из тех функций языка PHP, которые выглядят как функции.

Чтобы сузить это немного : я не интересен в перемещении массивов с шагом всего на 1 (без отрицательных шагов, т. Е. С обратной итерацией). Меня также не интересует обход к и от произвольных точек, всего от 0 до длины. Я также не вижу манипуляции массивами с более чем 1000 ключами, происходящими на регулярной основе, но я вижу, что массив проходит несколько раз в логике приложения! Кроме того, что касается операций, в основном, только строковые манипуляции и эхо-сигналы.

Вот несколько ссылочных сайтов:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php

То, что я слышу везде:

  • foreach медленный, и, следовательно, for / while быстрее
  • PHP foreach копирует массив, который он выполняет; чтобы сделать это быстрее, вам нужно использовать ссылки
  • код вроде этого: $key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    $key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    $key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    быстрее, чем foreach

Вот моя проблема. Я написал этот тестовый скрипт: http://pastebin.com/1ZgK07US и независимо от того, сколько раз я запускаю скрипт, я получаю что-то вроде этого:

 foreach 1.1438131332397 foreach (using reference) 1.2919359207153 for 1.4262869358063 foreach (hash table) 1.5696921348572 for (hash table) 2.4778981208801 

Вкратце:

  • foreach быстрее, чем foreach со ссылкой
  • foreach быстрее, чем for
  • foreach быстрее, чем for хэш-таблицы

Может кто-нибудь объяснить?

  1. Я делаю что-то неправильно?
  2. Является ли справочная вещь PHP foreach действительно имеющей значение? Я имею в виду, почему бы не скопировать его, если вы пройдете по ссылке?
  3. Каков эквивалентный код итератора для инструкции foreach; Я видел несколько в сети, но каждый раз, когда я тестирую их, время уходит; Я также проверил несколько простых конструкций итератора, но никогда не получал даже достойных результатов – атераторы массива на PHP просто ужасны?
  4. Существуют ли более быстрые способы / методы / конструкции для итерации, кроме массива, отличного от FOR / FOREACH (и WHILE)?

PHP версия 5.3.0


Edit: Answer С помощью людей здесь я смог собрать ответы на все вопросы. Я приведу их здесь:

  1. "Я делаю что-то неправильно?" Консенсус, похоже, таков: да, я не могу использовать эхо в тестах. Лично я до сих пор не вижу, как эхо – это некоторая функция со случайным временем выполнения или как любая другая функция каким-то образом отличается от того, что и способность этого скрипта просто генерировать точные результаты foreach лучше, чем все сложно объяснять, хотя просто «вы используете эхо» (ну, что я должен был использовать). Однако я соглашаюсь, что тест должен быть сделан с чем-то лучшим; хотя идеальный компромисс не приходит в голову.
  2. «Является ли ссылка PHP foreach действительно важной для меня? Я имею в виду, почему она не копирует ее, если вы пройдете по ссылке?» ircmaxell показывает, что да, дальнейшее тестирование, похоже, доказывает, что в большинстве случаев ссылка должна быть быстрее – хотя, учитывая мой вышеприведенный фрагмент кода, определенно это не значит все. Я согласен, что проблема, вероятно, слишком неинтуитивная, чтобы беспокоиться на таком уровне и требовать чего-то экстремального, такого как декомпиляция, чтобы фактически определить, что лучше для каждой ситуации.
  3. «Каков эквивалентный код итератора для оператора foreach, я видел несколько в сети, но каждый раз, когда я тестирую их, время уходит, я также тестировал несколько простых конструкций итератора, но никогда не получал даже приличных результатов – Итераторы массива на PHP просто ужасны? " ircmaxell дал ответ ниже; хотя код может быть действителен только для версии PHP> = 5
  4. «Существуют ли более быстрые способы / методы / конструкции для итерации, кроме массива, отличного от FOR / FOREACH (и WHILE)?» Спасибо, Гордон за ответ. Использование новых типов данных в PHP5 должно давать либо повышение производительности, либо увеличение памяти (любой из которых может быть желательным в зависимости от вашей ситуации). В то время как скорость мудрее многих новых типов массивов не кажется лучше, чем array (), splpriorityqueue и splobjectstorage выглядят значительно быстрее. Ссылка, предоставленная Гордоном: http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/

Спасибо всем, кто пытался помочь.

Я, скорее всего, придерживаюсь foreach (версия без ссылки) для любого простого обхода.

Мое личное мнение – использовать то, что имеет смысл в контексте. Лично я почти никогда не использую for обхода массива. Я использую его для других типов итераций, но foreach слишком просто … В большинстве случаев разница во времени будет минимальной.

Самое главное наблюдать:

 for ($i = 0; $i < count($array); $i++) { 

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

Что касается ссылки, которая делает разницу, PHP использует copy-on-write, поэтому, если вы не пишете массив, при циклизации будет относительно мало накладных расходов. Однако, если вы начнете изменять массив в массиве, вот где вы начнете видеть различия между ними (так как нужно будет скопировать весь массив, а ссылка может просто изменить встроенную) …

Что касается итераторов, foreach эквивалентен:

 $it->rewind(); while ($it->valid()) { $key = $it->key(); // If using the $key => $value syntax $value = $it->current(); // Contents of loop in here $it->next(); } 

Поскольку существуют более быстрые способы повторения, это действительно зависит от проблемы. Но мне действительно нужно спросить, почему? Я понимаю, что хочу сделать вещи более эффективными, но я думаю, вы тратите свое время на микро-оптимизацию. Помните, Premature Optimization Is The Root Of All Evil

Редактировать: Основываясь на комментарии, я решил сделать быстрый тест …

 $a = array(); for ($i = 0; $i < 10000; $i++) { $a[] = $i; } $start = microtime(true); foreach ($a as $k => $v) { $a[$k] = $v + 1; } echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => &$v) { $v = $v + 1; } echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => $v) {} echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => &$v) {} echo "Completed in ", microtime(true) - $start, " Seconds\n"; 

И результаты:

 Completed in 0.0073502063751221 Seconds Completed in 0.0019769668579102 Seconds Completed in 0.0011849403381348 Seconds Completed in 0.00111985206604 Seconds 

Поэтому, если вы изменяете массив в цикле, в несколько раз быстрее использовать ссылки …

И накладные расходы для просто ссылки на самом деле меньше, чем копирование массива (это на 5.3.2) … Таким образом, он появляется (по крайней мере, 5.3.2), как если бы ссылки значительно быстрее …

Я не уверен, что это так удивительно. Большинство людей, которые программируют на PHP, не очень разбираются в том, что на самом деле делает PHP на голом металле. Я расскажу несколько вещей, которые будут выполняться большую часть времени:

  1. Если вы не изменяете эту переменную, то в PHP значение по-прежнему выполняется быстрее. Это связано с тем, что в любом случае эта ссылка считается подсчитанной, а ее значение дает меньше. Он знает, что во втором случае вы измените эту ZVAL (внутренняя структура данных PHP для большинства типов), она должна будет отключить ее простым способом (скопируйте ее и забудьте о другом ZVAL). Но вы никогда не изменяете его, так что это не имеет значения. Ссылки делают это более сложным с большей ведомостью, которую он должен делать, чтобы знать, что делать, когда вы изменяете переменную. Поэтому, если вы читаете только, как это ни парадоксально, лучше не говорить об этом с &. Я знаю, это противоречит интуиции, но это также верно.

  2. Дело не медленное. И для простой итерации условие, которое оно тестирует, – «Я в конце этого массива» – выполняется с использованием собственного кода, а не кода PHP. Даже если это APC-кешированные коды операций, он все еще медленнее, чем куча собственных операций, выполненных на голой поверхности.

  3. Использование цикла for for for ($ i = 0; $ i <count ($ x); $ i ++) является медленным из-за count () и отсутствия способности PHP (или действительно любого интерпретируемого языка) для оценки при анализе время, изменяет ли какой-либо элемент массив. Это не позволяет ему оценивать счет один раз.

  4. Но даже после того, как вы исправите его с помощью «$ c = count ($ x)), для ($ i = 0; $ i <$ c; $ i ++) $ i <$ c – это куча опкодов Zend в лучшем случае, как и $ i ++. В ходе 100000 итераций это может иметь значение. Foreach знает на собственном уровне, что делать. Никаких PHP-кодов, необходимых для проверки состояния «я в конце этого массива».

  5. А как насчет старой школы? While (list («stuff?» Ну, используя each (), current () и т. Д., Все будут задействованы как минимум один вызов функции, который не медленный, но не бесплатный. PHP opcodes снова! Так что, хотя + list + у каждого есть свои затраты.

По этим причинам foreach, по понятным причинам, является лучшим вариантом для простой итерации.

И не забывайте, что это также проще всего читать, поэтому это беспроигрышный.

Одна вещь, которой нужно следить в тестах (особенно phpbench.com), даже несмотря на то, что цифры звучат, тестов нет. Многие тесты на phpbench.com делают вещи в тривиальном режиме и злоупотребляют способностью PHP кэшировать поиск массива в искажениях тестов или, в случае итерации по массиву, фактически не проверяют его в реальных случаях (никто не пишет пусто для петли). Я сделал свои собственные тесты, которые, как я нашел, достаточно отражают результаты реального мира, и они всегда показывают собственный синтаксис языка для итерации, выходящий сверху (неожиданность, неожиданность).

 //make a nicely random array $aHash1 = range( 0, 999999 ); $aHash2 = range( 0, 999999 ); shuffle( $aHash1 ); shuffle( $aHash2 ); $aHash = array_combine( $aHash1, $aHash2 ); $start1 = microtime(true); foreach($aHash as $key=>$val) $aHash[$key]++; $end1 = microtime(true); $start2 = microtime(true); while(list($key) = each($aHash)) $aHash[$key]++; $end2 = microtime(true); $start3 = microtime(true); $key = array_keys($aHash); $size = sizeOf($key); for ($i=0; $i<$size; $i++) $aHash[$key[$i]]++; $end3 = microtime(true); $start4 = microtime(true); foreach($aHash as &$val) $val++; $end4 = microtime(true); echo "foreach ".($end1 - $start1)."\n"; //foreach 0.947947025299 echo "while ".($end2 - $start2)."\n"; //while 0.847212076187 echo "for ".($end3 - $start3)."\n"; //for 0.439476966858 echo "foreach ref ".($end4 - $start4)."\n"; //foreach ref 0.0886030197144 //For these tests we MUST do an array lookup, //since that is normally the *point* of iteration //i'm also calling noop on it so that PHP doesn't //optimize out the loopup. function noop( $value ) {} //Create an array of increasing indexes, w/ random values $bHash = range( 0, 999999 ); shuffle( $bHash ); $bstart1 = microtime(true); for($i = 0; $i < 1000000; ++$i) noop( $bHash[$i] ); $bend1 = microtime(true); $bstart2 = microtime(true); $i = 0; while($i < 1000000) { noop( $bHash[$i] ); ++$i; } $bend2 = microtime(true); $bstart3 = microtime(true); foreach( $bHash as $value ) { noop( $value ); } $bend3 = microtime(true); echo "for ".($bend1 - $bstart1)."\n"; //for 0.397135972977 echo "while ".($bend2 - $bstart2)."\n"; //while 0.364789962769 echo "foreach ".($bend3 - $bstart3)."\n"; //foreach 0.346374034882 

Я думаю, но я не уверен: цикл for выполняет две операции для проверки и увеличения значений. foreach загружает данные в память, тогда он будет перебирать все значения.