Производительность 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 быстрее
  • PHPs foreach копирует массив, в котором он итерации; чтобы сделать это быстрее, вам нужно использовать ссылки
  • : $key = array_keys($aHash); $size = sizeOf($key);
    for ($i=0; $i < $size; $i++)
    быстрее, чем foreach

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

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 для хеш-таблицы

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

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

PHP версия 5.3.0


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

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

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

Ответ 1

Мое личное мнение - использовать то, что имеет смысл в контексте. Лично я почти никогда не использую 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), как если бы ссылки значительно быстрее...

Ответ 2

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

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

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

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

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

  • Как насчет старой школы? while (list ( "stuff?", используя каждый(), current() и т.д., все будут задействованы как минимум один вызов функции, который не медленный, но не бесплатно. Да, это PHP-коды снова! Так что в то время как + list + каждый имеет свои затраты.

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

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

Ответ 3

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

//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

Ответ 4

 i think but i am not sure 
 ======================
 for loop two more operation for checking, increment values. but
 foreach is first time load data in memory then it will travel next next values.