Ниже приведен тест петли 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
Я также собрал набор тестов. Но я более конкретно ставил код в тестовые функции.
for
(не count
)? 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, даже если значения будут поддерживаться каким-то образом движком. Таким образом, штраф.