php foreach,为什么使用数组引用传递速度快?

时间:2011-10-16 19:03:07

标签: php arrays 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

但这一次,使用传递参考的速度很慢。

代码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

有人可以解释为什么通过引用传递在code1中速度快但在code2中速度慢吗?

修改 使用代码2,count($a)产生了主要区别,因此循环所用的时间几乎相同。

2 个答案:

答案 0 :(得分:7)

  

我认为如果$v不改变[foreach($a as $v)],真正的副本将不会因为写入时复制而发生,但是为什么它会很快通过引用传递?

影响不在$v上,而在$a,巨大的阵列上。您可以将其作为值传递或作为函数的引用传递。在函数内部,它是值(test1)或引用(test2)。

您有两个代码(代码1和代码2)。

代码1:正在使用foreach。使用foreach,您有两个选项:迭代值或引用(Example)。迭代某个值时,迭代将在值的副本上完成。如果迭代引用,则不会进行复制。

在test2中使用引用时,速度更快。不需要复制值。但是在test1中,您将数组作为值传递,数组将被复制。

代码2 :正在使用for。因为在这里什么也没做。在这两种情况下。您可以从数组中访问变量和读取值。无论是引用还是副本,这几乎都是一样的(感谢PHP中的写入优化副本)。

您现在可能想知道,为什么 在代码2中存在差异。差异不在于for,而在于count。如果您传递对count的引用,PHP会在内部创建它的副本,因为它count需要副本,而不是引用。

同时阅读:Do not use PHP references by Johannes Schlüter


我也编译了一组测试。但我更具体地将代码放入测试函数中。

  • 空白 - 调用该函数有什么区别?
  • 计数 - count会有所作为吗?
  • For - 仅for(不是count)会发生什么?
  • Foreach - Just foreach - 甚至打破第一个元素。

每个测试都有两个版本,一个名为_copy(将数组作为副本传递给函数),另一个名为_ref(将数组作为参考传递)。

这些微观基准并不总是告诉你真相,但如果你能够隔离特定点,你可以做出有根据的猜测,例如不是for而是{{1}有影响:

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);

答案 1 :(得分:3)

实际上,我对第一个答案略有不同意见。最重要的是,正如评论所说,测试并不相同。这是完全隔离的测试,仅测试循环。

版本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执行时立即复制数组内部,即使引擎以某种方式维护这些值。因此,惩罚。