在PHP(> = 5.0)中,通过引用传递速度更快吗?


70

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

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

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

取而代之的是,PHP似乎使用写时复制来避免在对象被更改之前复制对象(也许还有数组)。因此,对于不更改其参数的函数,其效果应与您通过引用传递它们的效果相同。

但是,我想知道写时复制逻辑是否可能在传递引用时短路,并且这是否会对性能产生影响。

ETA:可以肯定的是,我认为它并不快,而且我很清楚这不是引用的目的。所以我想我自己的猜测很好,我是在寻找一个真正知道内幕下肯定正在发生什么的人的答案。在PHP开发的五年中,我总是发现很难通过阅读源代码来获取有关PHP内部的高质量信息。


1
见我的问题的一个例子,其中引用可以慢下来显着:stackoverflow.com/questions/3117604/...
约翰·卡特

Answers:


35

Zend Engine使用写时复制,当您自己使用引用时,会产生一些额外的开销。但是,仅在撰写本文时才能找到此提及,并且手册中的注释包含其他链接。

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


4
因此,您是说这实际上损害了性能(即使影响可能微不足道)?有趣,谢谢!
汉诺·菲茨

6
仅在您从未操纵原始数据结构的情况下,才发生(某种程度上的学术性)总体性能损失。当您计划时,实际上应该获得性能,因为您避免了写时复制。
Tomalak

3
是的,理所当然,这有点学术性。只是我由于误解PHP的内部工作方式而感到沮丧,这使我对找出答案有些痴迷。在我看来,关于PHP内部的良好来源,越来越难以找到比其他语言,如Python的
汉诺FIETZ

85

在一个具有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
这是非常有用的信息,但我很好奇:此测试使用的是哪个PHP版本?
Andrew Ensley '02

4
那真的没有多大意义。那是一些确实非常低效的引用处理。
Jonathon

请参阅下面的答案。它讨论了使用大型数组等。
Drew LeSueur 2013年

1
此测试是否考虑了值传递函数在更改后复制和返回值所花费的额外时间?
克里斯·米德尔顿

我已经注意到,当您count()在获取按引用数组的函数中获取数组的时,按引用传递很慢。
Drew LeSueur

29

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

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

这是我的结果: 基准测试

Y轴(行程)是一个函数在一秒钟内可以调用多少次* 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);}
克里斯·米德尔顿

在数组内部进行更改,并且返回更改并再次执行,通过引用返回并再次执行更改或不返回但由于再次获取引用参数而导致的变化时,本来很好。只是说。
hakre 2014年

6

我已经尝试了将10k字节字符串传递给两个相同函数的值和引用。一个以值作为参数,第二个以引用作为参数。它们是常见的功能-接受参数,进行简单处理并返回值。我对两者进行了10万次调用,并发现引用不是为了提高性能而设计的-引用的利润接近4-5%,并且只有当字符串变得足够大时才会增长(100k和更长的长度,可以提高6-7%) 。因此,我的结论是不使用引用来提高性能,这不是为了该目的。

我使用PHP版本5.3.1



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

0

我试图根据我正在研究的项目的真实示例对它进行基准测试。和往常一样,差异很小,但是结果有些出乎意料。对于我所见过的大多数基准测试,被调用函数实际上并不会更改传入的值。我对其执行了一个简单的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

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

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

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

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

如果您需要传递的数据大于ABI,则其余值将进入堆栈。在这种情况下,数组或对象(例如,它是一个类,或者是一个结构+头文件)总是会被引用更快。

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

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


PHP值是动态类型的,因此它们始终作为对描述该值和类型的结构的引用进行传递。变量是否通过引用传递在这里没有区别。
伊沃·史密斯

-3

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


1
或者,实际上,它们的表示方式已更改,因此无论如何传递的始终始终只是一个处理程序/引用/指针。但这不完全是我的问题。
汉诺·菲茨

7
-1不通过引用传递PHP 5中的对象。完全按照Java中的值传递它们。这里的关键是要理解变量不保存对象,而是指向对象的指针。因此,您通过值传递的内容(在PHP和Java中)是一个指针。
GetFree 2011年

这个问题并没有说明它仅与php对象有关。在某些情况下,绝对可以通过引用指定参数。(如果没有,那么引用运算符将​​不存在。)
ToolmakerSteve
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.