13

由于我的研究使我相信for循环是 PHP 中最快的迭代构造......为了更清楚,您认为以下哪个会更快?

示例一

for ($i = 0; $i < count($myLargeArray); $i++ ) {
    echo myLargeArray[$i];
}

示例二

$count = count($myLargeArray);
for ($i = 0; $i < $count; $i++ ) {
    echo myLargeArray[$i];
}

我的逻辑是,在示例中的每次迭代中,在每次迭代中访问 myLargeArray 的长度比访问示例二中的简单整数值的计算成本更高。那是对的吗?

4

6 回答 6

15

第一种方法较慢,因为count()必须在循环的每次迭代中调用该函数。该count()方法本身非常快,但是调用该函数仍然存在一些开销。通过将它移到循环之外,您正在执行所谓的“循环不变代码运动”,或者有时是“提升”。

有一系列像这样的优化很有趣。

说了这么多,很少强调这一点。在您的示例中,回显输出的 I/O 可能是您通过“优化”节省的 10 倍。如果你在循环中做任何其他事情,你的优化意味着越来越少。

我讨厌成为一个湿毯子,但是对于您 90% 以上的代码,性能不是问题。尤其是当您谈论 Web 应用程序时,它们一开始就超过 90% 的 I/O。

尽管如此,当您认为应该归咎于您的代码时,您应该:

  1. 确定需要优化的用例
  2. 衡量你的代码性能
  3. 找到瓶颈
  4. 确定您可以改进的领域,并决定是否值得您花时间改进它们。
  5. 进行代码更改
  6. 返回第 2 步

您几乎总是会发现您需要改进缓存策略和数据库优化(这只是通过另一种方式进行的 I/O 优化),而不是玩弄代码。

于 2012-11-20T04:52:39.330 回答
2

示例 2. 不要每次迭代都计算元素。

更新:我刚刚被告知该值是预先计算的: nNumOfElements specifies how many values are currently stored in the array. This is also the number thatcount($array)returns.

在我看来,这个函数count()除了浪费一些微秒和时钟周期(对于那些了解汇编程序的人来说)之外,实际上什么也没做。

在这里阅读:了解 PHP 的内部数组实现(PHP 的 PHP 开发人员源代码 - 第 4 部分)

也许您可以尝试foreach range

foreach (range(0, (count(array)) as $number) {
    echo $number;
}
于 2012-11-20T03:48:05.117 回答
2

在这种情况下,最快的构造实际上是 foreach 循环:

foreach($myLargeArray as $element) {
    echo $element;
} 

foreach() 也很好,因为它总是会终止,而当你使用 for() 时,拼写错误可能会给你留下一个无限循环。

于 2012-11-20T04:46:55.343 回答
0

显然,示例一较慢。每次迭代都会评估条件$i < count($myLargeArray),从而多次计算数组。

在http://www.phpbench.com/上检查这个和其他基准

编辑:他们查找了源代码,并且它是预先计算的。

但是,处理时间被浪费在那些多个函数调用上。这就是性能下降的原因。该数组被“计数”多次。

于 2012-11-20T03:48:42.253 回答
0

最快的循环是展开循环。一些代码编辑器,但不是任何 PHP 编辑器,通过特殊的宏支持这一点,因此您无需复制和粘贴。

于 2012-11-20T05:12:51.927 回答
0

所以我决定实际量化一些事情,以获得一些真实的数字。这是基线代码,一个循环构建一个包含 100000 个整数的大数组。

$x = array();
for ($idx=0; $idx<100000; $idx++)
    $x[] = $idx;

平均执行时间:85 毫秒。这包括启动 PHP、解析程序、运行程序和退出的时间。现在,我添加另一个循环遍历数组:

for ($idx=0; $idx<count($x); $idx++) { 
    ;
}

平均执行时间:105 毫秒。当您减去 85 毫秒的设置时间时,您可以看到迭代 100,000 个成员数组只需要 20 毫秒。

现在我们添加循环不变代码运动:

$m = count($x);
for($idx=0; $idx<$m; $idx++) { 
    ;
}

平均执行时间:90 毫秒。

一方面,这种节省是巨大的。那是 5 毫秒的循环迭代时间,而不是 20 毫秒。所以你可以说这是节省了 75%!

另一方面,它是 15 毫秒。比大多数人在一个荒谬的大阵列上注意到的时间要少。

但这是一个什么都不做的数组。让我们看看当我们输出一些数据时会发生什么:

$m = count($x);
for ($idx=0; $idx<$m; $idx++) { 
    echo $idx;
}

现在执行时间是 200 毫秒。哦,看,我只打印了循环索引。我什至没有输出数组的内容。

这很愚蠢。让我们再次更改程序以回显数组的内容,而不仅仅是查看计数器:

$m = count($x);
for ($idx=0; $idx<$m; $idx++)
    echo $x[$idx];

新的执行时间是 212 毫秒。因此,访问和回显数组内容比回显循环计数器要长 5%。

让我们接受某人先前的建议并展开循环。过去我在 C/C++ 中使用过这个效果很好:

$m = count($x);
for ($idx=0; $idx<$m; $idx+=5) {
    echo $x[$idx];
    echo $x[$idx+1];
    echo $x[$idx+2];
    echo $x[$idx+3];
    echo $x[$idx+4];
}

现在我们在说话!我们降至 206 毫秒。哦,等等,对于一些不好玩的代码来说,这大约是 3% 的改进。输出看起来很糟糕。它只是一串没有空格或任何东西的数字。

让我们摆脱循环展开,让输出更好一点:

$m = count($x);
for ($idx=0; $idx<$m; $idx++)
    echo "{$x[$idx]}\n";

执行时间为 400 毫秒。嗯。这需要很多额外的时间(相对而言)只是为了获得一些格式。也许使用字符串替换会让我们付出一些代价。让我们尝试字符串连接:

$m = count($x);
for ($idx=0; $idx<$m; $idx++)
    echo $x[$idx] . "\n";

新时间为 390 毫秒。好一点。让我们尝试用空格而不是换行符分隔数字:

$m = count($x);
for ($idx=0; $idx<$m; $idx++)
    echo $x[$idx] . " ";

哇哦,我们又回到了 224 毫秒。对了!但是发生了什么?好吧,我在我的 Unix 终端上运行所有这些,并且在单独的行上输出数字比在换行的一行上输出它们要慢得多。

换句话说,终端程序的滚动速度比我们所做的任何事情都具有更大的影响。

于 2012-11-20T05:56:56.397 回答