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

76

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

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

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

相反,PHP似乎使用Copy On Write来避免在更改对象(和可能也包括数组)之前复制它们。因此,对于不更改其参数的函数,效果应该与您按引用传递它们时相同。

但是,我想知道是否会在传递引用时短路Copy On Write逻辑,以及是否会对性能产生任何影响。

注意:为确保准确性,我假设这不会更快,并且我非常清楚这不是引用的用途。因此,我认为我的猜测相当准确,只是想从真正了解底层发生情况的人那里得到答案。在五年的PHP开发中,除了阅读源代码之外,我总是很难获得PHP内部的高质量信息。


1
请看我的问题,其中引用可能会严重拖慢速度的例子:https://dev59.com/w3A75IYBdhLWcg3wxcDV - John Carter
把一个大的数组通过引用传递和值传递有什么区别?是否与传递一个大的字符串相同的结果? - David Spector
9个回答

92

经过 100,000 次调用一个带有 20 kB 字符串参数的函数的测试,结果如下:

仅读取 / 使用参数的函数

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
这是很棒的信息,但我有个疑问:你在这个测试中使用的是哪个版本的PHP? - Andrew Ensley
7
这并没有太多意义。这是一种非常非常非常低效的引用处理方式。 - Jonathon
请参阅下面的答案。它讨论了如何使用大数组等问题。 - Drew LeSueur
1
这个测试是否考虑了按值传递函数在复制和返回值时所需的额外时间? - Chris Middleton
我注意到,当你在获取传递的引用数组的函数中使用count()函数时,传递引用会变慢。 - Drew LeSueur
答案没有说明作为参数传递的数组中包含什么类型的数据。我想这很重要。整数?字符串?其他? - David Spector

38

Zend引擎使用写时复制技术,当您自己使用一个引用时,会产生一些额外的开销。目前只能在这里找到相关信息,手册中的评论包含其他链接。

(编辑) 关于对象和引用的手册页面包含更多关于对象变量与引用之间区别的信息。


4
所以你的意思是它实际上会影响性能(尽管可能影响微不足道)?这很有趣,谢谢! - Hanno Fietz
7
只有在你从未操作原始数据结构时,才会出现略微学术化的整体性能损失。当你计划操作时,实际上应该会获得性能提升,因为你避免了写入时复制。 - Tomalak
3
是的,可以理解,这有点学术性。只是我因为不理解PHP内部工作原理而感到很沮丧,这使我有些过于追求细节。在我看来,相比其他语言(例如 Python),关于PHP内部的好资料更难找到。 - Hanno Fietz

31

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

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

这是我的测试结果: Benchmark

Y轴(Runs)表示每秒钟可以调用函数的次数*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
这个测试并不反映真实的使用情况。人们通常在无法返回多个值时通过引用传递,例如传递到错误数组的引用。更好的测试应该是这样的: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);} - Chris Middleton
很好的是,如果还有一些变化,比如在数组内部进行更改并返回更改后的结果,或者通过引用返回并再次获取,或者不返回但由于引用参数而再次获取,那就更好了。只是这么说。 - hakre
两个函数都不会改变数组中的数据。 - David Spector
我认为在“快速运行”的两个重要性能方面是CPU和内存。这总是两者之间的权衡。如果您处理大型数据集,当您通过值传递它们时,它们会在调用堆栈中多次使用而不是引用现有数据。依我之见,一个经验法则是 - 具有特定范围的大型数据?按引用传递。其他所有情况?尽可能按值传递以实现不可变性。 - domdambrogia

7
我尝试了传递包含10k字节字符串的值和引用,并将其传递给两个相同的函数。一个通过值传递参数,另一个通过引用传递参数。它们都是常见的函数 - 接受参数,进行简单处理并返回值。我分别调用这两个函数 100,000 次,并发现引用并不会提高性能 - 只有在字符串足够大时(100k及以上),引用才会带来4-5%的收益,而且只增长到6-7%。所以,我的结论是不要使用引用来提高性能,这种方法行不通。 我使用的是PHP版本5.3.1。

3
我非常确定不是更快。 此外,手册明确指出不要尝试使用引用来提高性能。
编辑:找不到它在哪里说了,但确实存在!

11
它说不要通过引用返回来提高性能。它并没有说不要通过引用传递来提高性能。http://www.php.net/manual/zh/language.references.return.php - Drew LeSueur

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更快,但可能不会快那么多。)


1

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

<?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
在进行基准测试时,请显示所得到的时间值。此外,由于您正在进行测试,您还应该测试一下您所声称的“如果没有任何值发生改变是无关紧要的”,否则读者将很难确定您测试了什么,仅仅是在做出断言。 - ToolmakerSteve

-2

很简单,没有必要测试任何东西。这取决于使用情况。

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

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

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

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

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

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


PHP的值是动态类型的,因此它们总是作为引用传递给描述值和类型的某些结构。无论变量是否通过引用传递,在这里都没有区别。 - Ivo Smits

-3

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


1
实际上,它们的表示方式已经被更改,以便传递的始终只是一个处理程序/引用/指针。但那并不完全是我的问题。 - Hanno Fietz
7
在 PHP 5 中,对象不是通过引用传递的,而是像 Java 一样按值传递。关键在于要理解变量不保存对象,而是指向对象的指针。因此,在 PHP 和 Java 中传递的是指针的值。 - GetFree
这个问题并没有明确指定它只涉及到php的对象。肯定有一些情况下参数应该通过引用来指定。(如果没有这种情况,那么引用运算符就不会存在。) - ToolmakerSteve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接