【问题标题】:In PHP (>= 5.0), is passing by reference faster?在 PHP (>= 5.0) 中,通过引用传递是否更快?
【发布时间】:2010-09-15 18:04:41
【问题描述】:

在 PHP 中,函数参数可以通过在函数声明中的参数前面加上一个 & 号来通过引用传递,如下所示:

function foo(&$bar)
{
    // ...
}

现在,我知道这并非旨在提高性能,而是允许函数更改通常超出其范围的变量。

相反,PHP 似乎使用 Copy On Write 来避免复制对象(可能还有数组),直到它们被更改。因此,对于不改变其参数的函数,其效果应该与您通过引用传递它们的效果相同。

但是,我想知道 Copy On Write 逻辑是否可能在 pass-by-reference 时发生短路,以及这是否会对性能产生任何影响。

ETA:可以肯定的是,我认为它不会更快,而且我很清楚这不是引用的用途。所以我认为我自己的猜测非常好,我只是在寻找一个真正知道幕后肯定发生了什么的人的答案。在 PHP 开发的五年中,我总是发现很难通过阅读源代码来获得有关 PHP 内部结构的优质信息。

【问题讨论】:

标签: php performance pass-by-reference


【解决方案1】:

很简单,不需要测试任何东西。 取决于用例。

对于少量参数,按值传递总是比参考值快。这取决于架构允许通过寄存器 (ABI) 传递多少变量。

例如 x64 将允许您通过寄存器传递 4 个 64 位值。 https://en.wikipedia.org/wiki/X86_calling_conventions

这是因为您不必取消引用指针,只需直接使用值即可。

如果您需要传递的数据大于 ABI,则其余值将进入堆栈。 在这种情况下,数组或对象(例如是类,或结构 + 标头)将始终比引用更快。

这是因为引用只是指向数据的指针(不是数据本身),大小固定,例如 32 位或 64 位,具体取决于机器。该指针将适合一个 CPU 寄存器。

PHP 是用 C/C++ 编写的,所以我希望表现相同。

【讨论】:

  • PHP 值是动态类型的,因此它们总是作为对描述值和类型的某些结构的引用传递。变量是否通过引用传递在这里没有区别。
【解决方案2】:

我尝试使用基于我正在从事的项目的真实示例对此进行基准测试。与往常一样,差异微不足道,但结果却有些出乎意料。对于我见过的大多数基准测试,被调用的函数实际上并没有改变传入的值。我对它执行了一个简单的 str_replace()。

**Pass by Value Test Code:**

$originalString=''; // 1000 pseudo-random digits

function replace($string) {
    return str_replace('1', 'x',$string);
}
$output = '';
/* set start time */
$mtime = microtime();
$mtime = explode(" ", $mtime);
$mtime = $mtime[1] + $mtime[0];
$tstart = $mtime;
set_time_limit(0);

for ($i = 0; $i < 10; $i++ ) {
    for ($j = 0; $j < 1000000; $j++) {
        $string = $originalString;
        $string = replace($string);
    }
}

/* report how long it took */
$mtime = microtime();
$mtime = explode(" ", $mtime);
$mtime = $mtime[1] + $mtime[0];
$tend = $mtime;
$totalTime = ($tend - $tstart);
$totalTime = sprintf("%2.4f s", $totalTime);
$output .= "\n" . 'Total Time' .
    ': ' . $totalTime;
$output .= "\n" . $string;
echo $output;

通过参考测试代码

除了

function replace(&$string) {
    $string = str_replace('1', 'x',$string);
}
/* ... */
replace($string);

几秒钟内的结果(1000 万次迭代):

PHP 5
    Value:     14.1007
    Reference: 11.5564

PHP 7
    Value:     3.0799
    Reference: 2.9489

每个函数调用的差异只有几分之一毫秒,但对于这个用例,在 PHP 5 和 PHP 7 中通过引用传递更快。

(注意:PHP 7 测试是在更快的机器上执行的——PHP 7 更快,但可能没有那么快。)

【讨论】:

    【解决方案3】:

    Zend 引擎使用写时复制,当您自己使用引用时,它会产生一些额外的开销。但在撰写本文时只能找到this mentionthe manual 中的 cmets 包含其他链接。

    (编辑)Objects and references 上的手册页包含有关对象变量与引用有何不同的更多信息。

    【讨论】:

    • 所以你是说它实际上会损害性能(即使影响可能非常微不足道)?这很有趣,谢谢!
    • 只有当你从不操纵原始数据结构时,才会出现(有点学术的)整体性能损失。当您计划这样做时,您实际上应该获得性能,因为您避免了写时复制。
    • 是的,当然,这有点学术性。只是我因误解 PHP 内部的工作原理而感到非常沮丧,这让我对找出答案有点迂腐。在我看来,PHP 内部的好资源比其他语言更难找到,例如蟒蛇
    【解决方案4】:

    没有什么比一段测试代码更好的了

    <?PHP
    $r = array();
    
    for($i=0; $i<500;$i++){
    $r[]=5;
    }
    
    function a($r){
    $r[0]=1;
    }
    function b(&$r){
    $r[0]=1;
    }
    
    $start = microtime(true);
    for($i=0;$i<9999;$i++){
      //a($r);
      b($r);
    }
    $end = microtime(true);
    
    echo $end-$start;
    ?>
    

    最终结果!数组越大(或调用次数越多),差异就越大。所以在这种情况下,通过引用调用更快,因为值在函数内部发生了变化。

    否则“按引用”和“按值”之间没有真正的区别,编译器足够聪明,不会在不需要时每次都创建新副本。

    【讨论】:

    • 说“解释器”而不是“编译器”可能更准确?
    • 当您进行基准测试时,请显示结果时间值。此外,由于您正在测试,因此您还应该测试您的声明,即如果没有更改任何值,则无关紧要。否则,读者无法轻易确定您测试了什么,您只是在断言什么。
    【解决方案5】:

    我已经尝试过将 10k 字节字符串的值和引用传递给两个相同的函数。一个按值取参数,第二个按引用取参数。它们是通用函数——接受参数、进行简单处理并返回一个值。我对两者都进行了 100 000 次调用,发现引用并非旨在提高性能 - 引用的利润接近 4-5%,并且只有当字符串变得足够大时才会增长(100k 或更长,提高了 6-7%) .所以,我的结论是不要使用引用来提高性能,这东西不是为了那个。

    我使用的是 PHP 5.3.1 版

    【讨论】:

      【解决方案6】:

      我对此进行了一些测试,因为我不确定给出的答案。

      我的结果表明,通过引用传递大型数组或字符串的速度明显更快。

      这是我的结果:

      Y 轴 (Runs) 是函数在 1 秒内可以调用多少次 * 10

      对每个函数/变量重复测试 8 次

      这是我使用的变量:

      $large_array = array_fill(PHP_INT_MAX / 2, 1000, 'a');
      $small_array = array('this', 'is', 'a', 'small', 'array');
      $large_object = (object)$large_array;
      $large_string = str_repeat('a', 100000);
      $small_string = 'this is a small string';
      $value = PHP_INT_MAX / 2;
      

      这些是函数:

      function pass_by_ref(&$var) {
      }
      
      function pass_by_val($var) {
      }
      

      【讨论】:

      • 不过,此测试并未反映真实的用例。当人们无法返回多个值时,通常会通过引用传递,例如传入对错误数组的引用。更好的测试应该是这样的:function pass_by_ref($val, &amp;$errors) { if($val &lt; 0) { $errors []= "val &lt; 0"; return false; } else return true; } ... 和 ... function pass_by_val($val, $errors) { if($val &lt; 0) { $errors []= "val &lt; 0"; return array("errors" =&gt; $errors, "result" =&gt; false); } else return array("errors" =&gt; $errors, "result" =&gt; true);}
      • 如果在数组内部发生更改并且更改返回并再次采用,通过引用返回并再次采用或不返回但由于再次采用引用的参数而有所变化,那就太好了。只是说。
      • 这两个函数都不改变数组中的数据。
      【解决方案7】:

      在使用 20 kB 的字符串调用函数进行 100 000 次迭代的测试中,结果是:

      只读取/使用参数的函数

      pass by value:      0.12065005 seconds
      pass by reference:  1.52171397 seconds
      

      写入/更改参数的功能

      pass by value:      1.52223396 seconds
      pass by reference:  1.52388787 seconds
      

      结论

      1. 按值传递参数总是更快

      2. 如果函数改变了传递的变量的值,实际上与按引用传递而不是按值传递是一样的

      【讨论】:

      • 这是很好的信息,但我很好奇:你用什么版本的 PHP 做这个测试?
      • 这并没有多大意义。这是一些非常非常低效的引用处理。
      • 请参阅下面的答案。它谈到了使用大型数组等等。
      • 此测试是否考虑了值传递函数在更改值时复制和返回值所花费的额外时间?
      • 我注意到,当您在获取按引用数组的函数中获取数组的count() 时,按引用传递很慢。
      【解决方案8】:

      传递对象时无需添加&运算符。在 PHP 5+ 中,对象是通过引用传递的。

      【讨论】:

      • 或者,实际上,它们的表示方式已经改变,因此传递的内容始终只是一个处理程序/引用/指针。但这不完全是我的问题。
      • -1 PHP 5 中的对象不是通过引用传递的。它们完全按照 Java 中的值传递。这里的关键是要理解变量不保存对象而是指向对象的指针。因此,您传递的值(在 PHP 和 Java 中)是一个指针。
      • 这个问题并没有说明它只是关于 php objects。在某些情况下,参数应该通过引用来指定。 (如果没有,那么引用运算符将​​不存在。)
      【解决方案9】:

      我很确定不会,它不会更快。 此外,它在手册中明确指出不要尝试使用引用来提高性能。

      编辑:找不到它说的地方,但它就在那里!

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多