在 PHP (>= 5.0) 中,通过引用传递是否更快?

In PHP (>= 5.0), is passing by reference faster?

提问人:Hanno Fietz 提问时间:10/7/2008 最后编辑:molfHanno Fietz 更新时间:2/25/2019 访问量:36682

问:

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

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

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

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

但是,我想知道写入时复制逻辑是否可能在通过引用时短路,以及这是否会对性能产生任何影响。

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

PHP 性能 通过引用

评论

1赞 John Carter 6/30/2010
请参阅我的问题,例如引用可以显着减慢速度:stackoverflow.com/questions/3117604/......
0赞 David Spector 1/20/2023
通过引用而不是按值传递大型数组怎么样?与传递大字符串的结果相同?

答:

3赞 Greg 10/7/2008 #1

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

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

评论

11赞 Drew LeSueur 10/17/2013
它说不要通过引用返回以提高性能。它并没有说不通过引用来提高性能。php.net/manual/en/language.references.return.php
38赞 Paul Dixon 10/7/2008 #2

Zend Engine 使用写入时复制,当您自己使用引用时,它会产生一些额外的开销。不过,只能在撰写本文时找到此提及,并且手册中的评论包含其他链接。

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

评论

4赞 Hanno Fietz 10/7/2008
所以你是说它实际上损害了性能(即使影响可能非常微不足道)?这很有趣,谢谢!
7赞 Tomalak 10/7/2008
只有当您从不操作原始数据结构时,才会发生(有点学术性的)整体性能损失。当你计划这样做时,你实际上应该提高性能,因为你避免了写入时复制。
3赞 Hanno Fietz 10/8/2008
是的,当然,这有点学术性。只是我因为误解了PHP内部的工作方式而感到非常沮丧,这让我有点迂腐。在我看来,PHP内部的好资源比其他语言(例如Python)更难找到
-3赞 Michał Niedźwiedzki 10/7/2008 #3

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

评论

1赞 Hanno Fietz 10/7/2008
或者,实际上,它们的表示方式已经改变,因此传递的内容始终只是一个处理程序/引用/指针。但这并不完全是我的问题。
7赞 GetFree 9/7/2011
-1 PHP 5 中的对象不是通过引用传递的。它们与在 Java 中完全按值传递。这里的关键是要理解变量不保存对象,而是指向对象的指针。因此,您传递的值(在 PHP 和 Java 中)是一个指针。
0赞 ToolmakerSteve 7/18/2020
该问题没有指定它仅与 php 对象有关。在某些情况下,肯定应该通过引用来指定参数。(如果没有,则引用运算符将不存在。
92赞 ikary 10/2/2010 #4

在调用字符串为 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. 如果函数更改了传递的变量的值,则出于实际目的,与通过引用传递相同,而不是通过值传递

评论

6赞 Andrew Ensley 2/24/2012
这是一个很好的信息,但我很好奇:你在这个测试中使用了什么版本的 PHP?
7赞 Jonathon 3/11/2013
这真的没有多大意义。这是一些非常非常低效的参考处理。
0赞 Drew LeSueur 10/17/2013
请参阅下面的答案。它谈到了使用大型数组等等。
1赞 Chris Middleton 9/12/2014
此测试是否考虑了按值传递函数在更改值时复制和返回值所需的额外时间?
0赞 Drew LeSueur 12/21/2016
我注意到,当您在获取按引用数组的函数中获取数组时,按引用传递的速度很慢。count()
31赞 Petah 7/5/2011 #5

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

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

以下是我的结果:Benchmark

Y 轴(运行)是函数在 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) {
}

评论

1赞 Chris Middleton 9/12/2014
但是,此测试并未反映实际用例。当人们无法返回多个值时,他们通常会通过引用传递,例如,传递对错误数组的引用。更好的测试是这样的:......和。。。。function pass_by_ref($val, &$errors) { if($val < 0) { $errors []= "val < 0"; return false; } else return true; }function pass_by_val($val, $errors) { if($val < 0) { $errors []= "val < 0"; return array("errors" => $errors, "result" => false); } else return array("errors" => $errors, "result" => true);}
0赞 hakre 12/25/2014
如果在数组内部更改了更改,并且返回并再次获取更改,通过引用返回并再次获取或由于再次获取引用的参数而未返回,那就太好了。只是说。
0赞 David Spector 2/24/2021
这两个函数都不会更改数组中的数据。
0赞 domdambrogia 10/13/2022
我认为有两个重要的性能要“走得快”。CPU + 内存。这始终是两者之间的权衡。如果要处理大型数据集,则在按值传递它们时,它们会在调用堆栈中多次使用,而不是引用现有数据。恕我直言,经验法则——具有特定范围的大数据?通过引用传递。一切?如果可能,按值传递不变性。
7赞 Vladimir Fesko 10/1/2011 #6

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

我用的是PHP版本5.3.1

1赞 Melsi 7/27/2014 #7

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

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

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

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

评论

6赞 mpet 9/12/2016
说“解释器”而不是“编译器”可能更准确?
0赞 ToolmakerSteve 7/18/2020
当您进行基准测试时,请显示生成的时间值。此外,由于您正在测试,因此您还应该测试您的声明,即如果没有更改值,则无关紧要。否则,读者无法轻易确定你测试了什么,你只是在断言什么。
2赞 Bob Ray 2/19/2018 #8

我试图用一个基于我正在从事的项目的真实例子来对此进行基准测试。与往常一样,差异是微不足道的,但结果有些出乎意料。对于我见过的大多数基准测试,调用的函数实际上不会更改传入的值。我对它执行了一个简单的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 更快,但可能没有那么快。

-2赞 yo3hcv 2/25/2019 #9

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

对于少量参数,按值传递将始终比引用更快。这取决于架构允许通过寄存器 (ABI) 传递的变量数量。

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

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

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

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

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

评论

0赞 Ivo Smits 3/8/2019
PHP 值是动态类型的,因此它们总是作为对描述值和类型的某种结构的引用传递。无论变量是否通过引用传递,这里都没有区别。