为什么在PHP中使用continue比使用break慢?

4
请考虑以下代码:
$start = microtime();
for($i = 2; $i < 100; $i++)
{
    for($y = 2; $y <= sqrt($i); $y++)
    {
        if($i%$y != 0)
        {
            continue;
        }
        else
        {
          continue 2;
        }
    }

    echo $i.',';
}
echo "\nFinished in " . (microtime() - $start);

鉴于上述代码有效地使用 continue 2 来中断内部循环并跳过任何内部循环后的代码,为什么下面的代码平均执行速度更快,尽管它似乎做了更多的工作:
 $start = microtime();
for($i = 2; $i < 100; $i++)
{
    $flag = true;
    for($y = 2; $y <= sqrt($i); $y++)
    {
        if($i%$y != 0)
        {
            continue;
        }
        else
        {
          $flag = false;
          break;
        }
    }

    if($flag === true) echo $i.',';
}
echo "\nFinished in " . (microtime() - $start);

感谢您的任何意见。

_____ 更新 ____________

感谢反馈,但我们似乎错过了重点。无论这是否是良好的编程实践,我都想知道为什么性能差异(虽然微小但一致)不在我预期的偏差范围内。

将true传递给microtime似乎并不重要,因为两个样本都使用相同的方法进行测量,具有相同的开销和相同的不准确性。

多次运行测试,如“平均”所暗示的那样。

仅出于说明目的,请考虑使用microtime(true)显示相同模式的以下小样本,与使用microtime()相同:

我知道这只是一个小样本,但模式非常清晰:

继续 0.00037288665771484 0.00048208236694336 0.00046110153198242 0.00039386749267578 0.0003662109375

中断 0.00033903121948242 0.00035715103149414 0.00033307075500488 0.00034403800964355 0.00032901763916016

感谢查看,并感谢任何进一步的反馈。

______ 更新 进一步研究 ____________

有趣的是,如果从代码中删除echo语句,则continue的性能更快,如果echo语句存在,则break更快。

请考虑以下代码示例,并考虑结果是否取决于是否删除了echo语句:

<?php
$breakStats = array();
$continueStats = array();

ob_start();
for($i = 0; $i < 10000; $i++)
{
   $breakStats[] = doBreakTest();
   $continueStats[] = doContinueTest();
}
ob_clean();

echo "<br/>Continue Mean " . (array_sum($continueStats) / count($continueStats));
echo "<br/>Break Mean " . (array_sum($breakStats) / count($breakStats));

function doBreakTest()
{
    $start = microtime(true);
    for($i = 2; $i < 100; $i++)
    {
        $flag = true;
        $root = sqrt($i);
        for($y = 2; $y <= $root; $y++)
        {
            if($i%$y != 0)
            {
                continue;
            }
            else
            {
                $flag = false;
                break;
            }
        }
    }

    if($flag === true) echo $i . '';
    return microtime(true) - $start;
}

function doContinueTest()
{
    $start = microtime(true);
    for($i = 2; $i < 100; $i++)
    {
        $root = sqrt($i);
        for($y = 2; $y <= $root; $y++)
        {
            if($i%$y != 0)
            {
                continue;
            }
            else
            {
                echo $i . '';
                continue 2;
            }
        }
    }
    return microtime(true) - $start;
}

存在Echo语句:

Continue平均值0.00014134283065796 Break平均值0.00012669243812561

不存在Echo语句:

Continue平均值0.00011746988296509 Break平均值0.00013022310733795

需要注意的是,从break和flag测试中删除echo语句还会删除($flag === true)检查,因此负载应该会减少,但在这种情况下,continue仍然获胜。因此,在纯粹的continue n与break + flag场景中,continue n似乎是更快的结构。但是,添加相同数量的相同echo语句后,continue n的性能会下降。

对我来说,这是有逻辑的,continue n应该更快,但我希望看到存在echo语句时的相同结果。

这显然是生成的操作码中的差异,以及echo语句的位置(内部循环与外部循环),是否有人知道一种查看生成的操作码的方法?我想理解内部发生了什么。

谢谢:)


输出结果一样吗?你能把它们粘贴在这里吗? - Preet Sangha
1
嗯,快多少呢?而且你应该使用microtime(TRUE)来获取一个适合比较的浮点数。 - mario
6
只进行一次基准测试毫无意义。执行10,000次脚本运行并取平均值以获得有用的数据。然后考虑它实际上快了多少(如果有的话),以及它对你来说是否真的很重要。 - Lightness Races in Orbit
在循环中应避免过度使用 continuebreak。考虑使用 while 代替。不要在 for 循环的测试表达式中使用函数。 - KingCrunch
1
它们在 PHP 5.3.6 上执行的时间几乎相同。不过这只是一个微基准测试,所以您可能会遇到的任何差异都是由于 PHP 内部实现方式造成的。 - Josh Davis
显示剩余7条评论
2个回答

2

是的,第一个更快一些。这是因为它只是在继续2时跳出并打印$i。

第二个示例需要执行更多工作...将值分配给$flag变量,跳出循环,检查$flag的值,检查$flag的类型(也进行比较),然后打印$i。它稍微慢一些(逻辑简单)。

无论如何,它有任何目的吗?

我的一些比较结果

0.0011570 < 0.0012173
0.0011540 < 0.0011754
0.0011820 < 0.0012036
0.0011570 < 0.0011693
0.0011970 < 0.0012790

使用的技术: PHP 5.3.5 @ Windows (尝试了1000次,第一次就成功了)

0.0011570 < 0.0012173
0.0005000 > 0.0003333
0.0005110 > 0.0004159
0.0003900 < 0.0014029
0.0003950 > 0.0003119
0.0003120 > 0.0002370

使用的技术: PHP 5.3.3 @ Linux (1000次尝试; 32%第一次 : 68%第二次更快)

0.0006700 > 0.0004863
0.0003470 > 0.0002591
0.0005360 > 0.0004027
0.0004720 > 0.0004229
0.0005300 > 0.0004366

使用: PHP 5.2.13 @ Linux (尝试了1000次; 9%的第一次 : 91%的第二次更快)

抱歉,我没有更多的服务器用于测试 :) 现在我认为它主要取决于硬件(也许还取决于操作系统)。

总体而言:它只证明Linux服务器比Windows运行得更快 :)


也许你是对的,但问题表明第一个更慢,而不是更快。 - KingCrunch
这可能是特定于我的确切硬件,从逻辑上讲我同意你的观点,因此提出了这个问题,但我看到的结果并非如此。我会尝试另一个盒子。 - Gavin
我在我的第三台服务器上运行了测试,发现第一次运行花费了9%的时间,而第二次运行花费了91%的时间,速度更快。 - Wh1T3h4Ck5
1
@Gavin:这是给你的伙计(10,000次尝试 x 3个服务器)。你是对的,第二个更快...快得多。http://www.service-kl.com/code/results.zip 我上次测试的完整结果,使用了microtime(true)进行改进。 - Wh1T3h4Ck5
非常感谢你提供所有这些统计数据!还满足了我的好奇心,真是太感激了。 - Gavin
显示剩余5条评论

2

continue 2版本对我来说稍微快一点。但这些不是你通常需要关注的类型。考虑:

for($y = 2; $y <= sqrt($i); $y++)

在这里,你在每次迭代中计算sqrt。只需将其更改为:
$sqrt = sqrt($i);
for($y = 2; $y <= $sqrt; $y++)

使用比在两个几乎相似的循环类型之间切换要好得多。

如果您发现使用continue 2更容易理解,那么应该使用它。计算机并不在意。

关于查看操作码的更新,请参见:

php -d vld.active=1 -d vld.execute=0 -f foo.php


优秀的观点提到了代码可读性和将计算从for循环初始化器中移出。为了将其放入上下文中,我们并不是真正寻求最快的实现方式。我们正在讨论面试问题的答案,在讨论中,我建议使用continue 2方法比break版本稍微好一些。我们进行了测试,在我的硬件和我们的服务器上,我错了。我只是不明白为什么。 - Gavin
@Gavin,如果你不讨论最快的方法,那么这个问题甚至不会被提出。 ;) 我理解好奇心,但是这些方法非常相似,几乎不可能以任何有意义的方式进行基准测试,特别是当它甚至没有接近瓶颈时。一个更好的测试(但仍然基本上无用)将是不包括任何回声语句的测试。 - Matthew
@Gavin,如果你想比较操作码,可以访问http://pecl.php.net/package/vld。但是我认为这并没有什么太大帮助。我认为更有用的是理解Zend引擎的工作原理以及现代编译器和计算机如何进行优化和预测。 - Matthew

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