Производительность FOR против FOREACH в PHP - PullRequest
122 голосов
/ 07 августа 2010

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

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

Toнемного сузим это : мне не интересно обходить массивы с шагом больше 1 (никаких отрицательных шагов тоже нет, т.е. обратная итерация).Я также не заинтересован в обходе и от произвольных точек, просто 0 к длине.Я также не вижу, что манипулирование массивами с более чем 1000 ключами происходит регулярно, но я вижу, что массив перебирается несколько раз в логике приложения!Также что касается операций, в основном только строковых манипуляций и echo'ing.

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

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

  • foreach работает медленно, и, следовательно, for / while работает быстрее
  • PHPs foreach копирует массив, по которому он повторяется;чтобы сделать это быстрее, вам нужно использовать код ссылки
  • , например: $key = array_keys($aHash); $size = sizeOf($key); <br />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 Редактировать: Ответить С помощью людей здесь я смог собрать воедино ответы на все вопросы.Я суммирую их здесь:

  1. «Я что-то не так делаю?» Похоже, консенсус таков: да, я не могу использовать echo в тестах производительности.Лично я до сих пор не понимаю, как эхо - это какая-то функция со случайным временем выполнения или как любая другая функция как-то отличается - это и способность этого скрипта просто генерировать точно такие же результаты 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 (нереференсная версия) для любого простого обхода.

Ответы [ 4 ]

101 голосов
/ 07 августа 2010

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

Главное, на что нужно обратить внимание:

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

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

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

Что касается итераторов, 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) кажется, что ссылки значительно быстрее ...

49 голосов
/ 01 декабря 2012

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

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

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

  3. Использование цикла for "for ($ i = 0; $ i

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

  5. А как насчет старой школы "while (list (" вещи? Что ж, использование each (), current () и т. Д.) Будет включать как минимум 1 вызов функции, что не медленно, но не бесплатно. Да, это снова PHP-коды операций! Так что пока + list + каждый тоже имеет свои затраты.

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

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

28 голосов
/ 08 августа 2010

В тестах производительности (особенно 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
1 голос
/ 11 октября 2017

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...