php foreach, почему быстро использовать пропуск по ссылке массива?

Ниже приведен тест петли php foreach большого массива, я думал, что если переменная $v не изменится, реальная копия не произойдет из-за копирования при записи , но почему она быстро проходит по ссылке?

Код 1:

 function test1($a){ $c = 0; foreach($a as $v){ if($v=='xxxxx') ++$c; } } function test2(&$a){ $c = 0; foreach($a as $v){ if($v=='xxxxx') ++$c; } } $x = array_fill(0, 100000, 'xxxxx'); $begin = microtime(true); test1($x); $end1 = microtime(true); test2($x); $end2 = microtime(true); echo $end1 - $begin . "\n"; //0.03320002555847 echo $end2 - $end1; //0.02147388458252 

Но на этот раз использование pass by reference медленное.

Код 2:

 function test1($a){ $cnt = count($a); $c = 0; for($i=0; $i<$cnt; ++$i) if($a[$i]=='xxxxx') ++$c; } function test2(&$a){ $cnt = count($a); $c = 0; for($i=0; $i<$cnt; ++$i) if($a[$i]=='xxxxx') ++$c; } $x = array_fill(0, 100000, 'xxxxx'); $begin = microtime(true); test1($x); $end1 = microtime(true); test2($x); $end2 = microtime(true); echo $end1 - $begin . "\n"; //0.024326801300049 echo $end2 - $end1; //0.037616014480591 

Может ли кто-нибудь объяснить, почему передача по ссылке быстро в коде1, но медленная в коде2?

Изменить: с кодом 2, count($a) делает основное различие, поэтому время цикла занимает почти то же самое.

Я думал, что если $v не изменится [ foreach($a as $v) ], реальная копия не произойдет из-за копирования при записи , но почему она быстро проходит по ссылке?

Влияние не на $v а на $a , огромный массив. Вы либо передаете его как значение, либо как ссылку на функцию. Внутри функции это значение (test1) или reference (test2).

У вас есть два кода (код 1 и код 2).

Код 1: Используется foreach . С foreach у вас есть два варианта: перебрать значение или ссылку ( пример ). Когда вы перебираете значение, итерация выполняется на копии значения. Если вы повторяете ссылку, копия не будет выполнена.

Когда вы используете ссылку в test2, это быстрее. Значения не нужно копировать. Но в test1 вы передаете массив как значение, массив копируется.

Код 2: Используется for . Ибо ничего действительно здесь нет. В обоих случаях. Вы получаете доступ к переменной и читаете значение из массива. Это почти то же самое, независимо от того, является ли это ссылкой или копией (благодаря копированию на оптимизацию записи в PHP).

Теперь вы можете задаться вопросом, почему существует разница в коде 2. Разница не из- for а из-за count . Если вы передаете ссылку на count PHP внутренне создает копию, потому что для count требуется копия, а не ссылка.

Читайте также: не используйте ссылки PHP Johannes Schlüter


Я также собрал набор тестов. Но я более конкретно ставил код в тестовые функции.

  • Blank – Какая разница в вызове функции?
  • Count – подсчеты имеют значение?
  • Для – Что происходит только for (не count )?
  • Foreach – просто foreach – даже разбивая первый элемент.

Каждый тест состоит из двух версий, один называется _copy (передача массива в качестве копии в функцию), а один называется _ref (передача массива в качестве ссылки).

Не всегда эти микро-тесты говорят вам правду, но если вы можете выделить определенные моменты, вы вполне можете сделать обоснованное предположение, например, что не for а for count было влияние:

 function blank_copy($a){ } function blank_ref(&$a){ } function foreach_copy($a){ foreach($a as $v) break; } function foreach_ref(&$a){ foreach($a as $v) break; } function count_copy($a){ $cnt = count($a); } function count_ref(&$a){ $cnt = count($a); } function for_copy($a){ for($i=0;$i<100000;$i++) $a[$i]; } function for_ref(&$a){ for($i=0;$i<100000;$i++) $a[$i]; } $tests = array('blank_copy', 'blank_ref', 'foreach_copy', 'foreach_ref', 'count_copy', 'count_ref', 'for_copy', 'for_ref'); $x = array_fill(0, 100000, 'xxxxx'); $count = count($x); $runs = 10; ob_start(); for($i=0;$i<10;$i++) { shuffle($tests); foreach($tests as $test) { $begin = microtime(true); for($r=0;$r<$runs;$r++) $test($x); $end = microtime(true); $result = $end - $begin; printf("* %'.-16s: %f\n", $test, $result); } } $buffer = explode("\n", ob_get_clean()); sort($buffer); echo implode("\n", $buffer); 

Вывод:

 * blank_copy......: 0.000011 * blank_copy......: 0.000011 * blank_copy......: 0.000012 * blank_copy......: 0.000012 * blank_copy......: 0.000012 * blank_copy......: 0.000015 * blank_copy......: 0.000015 * blank_copy......: 0.000015 * blank_copy......: 0.000015 * blank_copy......: 0.000020 * blank_ref.......: 0.000012 * blank_ref.......: 0.000012 * blank_ref.......: 0.000014 * blank_ref.......: 0.000014 * blank_ref.......: 0.000014 * blank_ref.......: 0.000014 * blank_ref.......: 0.000015 * blank_ref.......: 0.000015 * blank_ref.......: 0.000015 * blank_ref.......: 0.000015 * count_copy......: 0.000020 * count_copy......: 0.000022 * count_copy......: 0.000022 * count_copy......: 0.000023 * count_copy......: 0.000024 * count_copy......: 0.000025 * count_copy......: 0.000025 * count_copy......: 0.000025 * count_copy......: 0.000026 * count_copy......: 0.000031 * count_ref.......: 0.113634 * count_ref.......: 0.114165 * count_ref.......: 0.114390 * count_ref.......: 0.114878 * count_ref.......: 0.114923 * count_ref.......: 0.115106 * count_ref.......: 0.116698 * count_ref.......: 0.118077 * count_ref.......: 0.118197 * count_ref.......: 0.123201 * for_copy........: 0.190837 * for_copy........: 0.191883 * for_copy........: 0.193080 * for_copy........: 0.194947 * for_copy........: 0.195045 * for_copy........: 0.195944 * for_copy........: 0.198314 * for_copy........: 0.198878 * for_copy........: 0.200016 * for_copy........: 0.227953 * for_ref.........: 0.191918 * for_ref.........: 0.194227 * for_ref.........: 0.195952 * for_ref.........: 0.196045 * for_ref.........: 0.197392 * for_ref.........: 0.197730 * for_ref.........: 0.201936 * for_ref.........: 0.207102 * for_ref.........: 0.208017 * for_ref.........: 0.217156 * foreach_copy....: 0.111968 * foreach_copy....: 0.113224 * foreach_copy....: 0.113574 * foreach_copy....: 0.113575 * foreach_copy....: 0.113879 * foreach_copy....: 0.113959 * foreach_copy....: 0.114194 * foreach_copy....: 0.114450 * foreach_copy....: 0.114610 * foreach_copy....: 0.118020 * foreach_ref.....: 0.000015 * foreach_ref.....: 0.000016 * foreach_ref.....: 0.000016 * foreach_ref.....: 0.000016 * foreach_ref.....: 0.000018 * foreach_ref.....: 0.000019 * foreach_ref.....: 0.000019 * foreach_ref.....: 0.000019 * foreach_ref.....: 0.000019 * foreach_ref.....: 0.000020 

Собственно, я не согласен с первым ответом. Самое главное, как отмечают в комментариях, тесты не совпадают. Вот полностью изолированные тесты, тестирование ТОЛЬКО петель.

Версия 1:

 <?php function test1($a) { $c = 0; $begin = microtime(true); foreach ($a as $v) { if ($v == 'x') ++$c; } $end = microtime(true); echo $end - $begin . "\n"; return $c; } function test2(&$a) { $c = 0; $begin = microtime(true); foreach ($a as $v) { if ($v == 'x') ++$c; } $end = microtime(true); echo $end - $begin . "\n"; return $c; } $x = array_fill(0, 1000000, 'x'); test1($x); // 0.11617302894592 test2($x); // 0.059789180755615 

Версия 2:

 <?php function test1($a) { $cnt = count($a); $c = 0; $begin = microtime(true); for ($i = 0; $i < $cnt; ++$i) if ($a[$i] == 'x') ++$c; $end = microtime(true); echo $end - $begin . "\n"; return $c; } function test2(&$a) { $cnt = count($a); $c = 0; $begin = microtime(true); for ($i = 0; $i < $cnt; ++$i) if ($a[$i] == 'x') ++$c; $end = microtime(true); echo $end - $begin . "\n"; return $c; } $x = array_fill(0, 1000000, 'x'); test1($x); // 0.086347818374634 test2($x); // 0.086491107940674 

Обратите внимание, что в полностью изолированной форме во вторых тестах нет различий, а первый – нет. Зачем?

Ответ заключается в том, что массив имеет внутренний указатель на такие вещи, как foreach. Доступ к нему возможен через вызовы типа current . Когда вы делаете foreach с ссылкой, используются указатели исходного массива. Когда вы передаете по значению, внутренние элементы массива должны быть скопированы, как только будет выполняться foreach, даже если значения будут поддерживаться каким-то образом движком. Таким образом, штраф.