下面是对一个大数组的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)
产生了主要区别,因此循环所用的时间几乎相同。
答案 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
(不是count
)会发生什么?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执行时立即复制数组内部,即使引擎以某种方式维护这些值。因此,惩罚。