php foreach, почему использование прохода по ссылке на массив быстро? - PullRequest
8 голосов
/ 16 октября 2011

Ниже приведен тест цикла 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

Но на этот раз передача по ссылке идет медленно.

Код 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?

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

Ответы [ 2 ]

7 голосов
/ 17 октября 2011

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

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

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

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

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

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

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

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


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

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

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

Не всегда эти микро-тесты говорят вам правду, но если вы можете выделить конкретные точки, вы вполне можете сделать обоснованное предположение, например, что не 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
3 голосов
/ 17 октября 2011

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

Версия 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, даже если значения каким-то образом поддерживаются механизмом. Таким образом, штраф.

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