【问题标题】:FOR loop performance in PHPPHP 中的 FOR 循环性能
【发布时间】:2012-11-08 02:39:35
【问题描述】:

由于我的研究让我相信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 的长度比访问示例 2 中的简单整数值的计算成本更高。对吗?

【问题讨论】:

  • 每次优化之前都应该进行分析。你分析过你的代码吗?
  • “在计算上比访问示例二中的简单整数值更昂贵” --- 但该值需要一些内存来存储。
  • foreach 然后忘记

标签: php performance loops for-loop


【解决方案1】:

所以我决定实际量化一些事情,以获得一些实数。这是基线代码,一个循环构建一个包含 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 终端上运行所有这些,并且在单独的行上输出数字比在换行的一行上输出它们要慢得多。

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

【讨论】:

  • 就像我说的一些代码编辑器有宏来展开循环。如果您有空闲时间或者可能有一些东西,您可以编写一个 php 扩展。我认为 facebook hiphop 的诞生就是因为这个:速度。还有遗传算法或机器学习呢?还是 php 中的分形?
  • 那么代码会更重要。对于人们几乎一直在做的几乎所有事情,代码效率并不是他们需要担心的:它是 I/O。就个人而言,如果我需要生成某种具有出色性能的分形图像,我会用 C 语言编写它,并将其作为 PHP 插件提供。
  • 我不明白一个想法。英语不是我的母语。我不认为优化代码是错误的,即使它是微优化的。但我也知道它不会支付你的账单。编程的另一件事是,这不仅仅是软件,它也是关于疯狂的机器。当您查看诸如 overclock.net 之类的网站以及人们在超频和冷却等方面所付出的努力时,为什么还要使用软件呢?仅仅因为它只有3%的收益就不再值得了吗?当您的代码工作并完成工作时,为什么不对其进行优化,尤其是当其他人使用它时?
  • 优化您的代码并为您的工作质量感到自豪并没有错。但是如果您希望您的 PHP Web 应用程序运行得更快,您必须了解代码性能通常只占性能问题的 10%。其他 90% 的性能问题通过缓存控制标头、持久数据库连接、APC/memcached 和查询优化来解决——所有这些都旨在减少 I/O。
  • 你必须与众不同。我的私人服务器比我的公司服务器好得多。更安全、更快、装备更好。还有其他一些问题,比如分形,它比 I/O 更与数学相关。我只是想知道为什么硬件人员似乎更有趣。也许是因为我这样做是为了生活。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

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

    在这里阅读:Understanding PHP's internal array implementation (PHP's Source Code for PHP Developers - Part 4)

    或许你可以试试foreach range

    foreach (range(0, (count(array)) as $number) {
        echo $number;
    }
    

    【讨论】:

    • 实际上并不是每个循环都计算元素。 count() 不会对数组进行物理迭代。
    • 什么意思?你的意思是我混淆了循环和迭代?
    • 您说“不要计算每个循环的元素”。我说count() 语言构造不计算 任何东西,它只是返回预计算 值。更多信息:nikic.github.com/2012/03/28/… (ctrl+f for 'nNumOfElements')
    • 函数调用的开销相对较大,因此确实会减慢循环速度。 PHP 编译器不会做很多优化,所以每次迭代都会调用该函数,这在 C/C++ 中不会出现。
    • @zerkms:计数是指计算元素。编号是你给他们符号的时候。这就是为什么程序员很难处理 null、0 和 1。指针和内存地址以及数字 0 混淆。实际上这份工作是sh1t。报酬低,名声不好。
    【解决方案4】:

    第一种方法比较慢,因为count() 函数必须在循环的每次迭代中调用。 count() 方法本身非常快,但是调用该函数仍然存在一些开销。通过将其移出循环,您正在执行所谓的“loop invariant code motion”,或者有时是“提升”。

    有一整套像这样的family of optimizations 值得学习。

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

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

    不过,当您认为应该归咎于您的代码时,您应该:

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

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

    【讨论】:

    • 我不同意。展开循环。
    • @Skidrow 展开并没有多大帮助。请参阅我对这个问题的第二个答案。
    【解决方案5】:

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

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

    foreach() 也很好,因为它总是会终止,而当您使用 for() 时,拼写错误可能会导致无限循环。

    【讨论】:

    • foreach 范围呢?然后他还有一个计数器变量。顺便提一句。最快的是完全没有循环,你也可以展开循环。
    • 并非如此。 foreach() 在链表上移动元素,这就是它更快的原因。即使您展开了 for 循环,您仍将通过数组的哈希表访问元素。
    【解决方案6】:

    显然,示例一较慢。每次迭代都会评估条件$i &lt; count($myLargeArray),从而对数组进行多次计数。

    http://www.phpbench.com/ 上检查此基准和其他基准

    编辑:他们查找了source code,它是预先计算好的。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-28
      • 1970-01-01
      • 1970-01-01
      • 2012-11-18
      • 2017-03-01
      • 2012-08-22
      • 2011-04-28
      相关资源
      最近更新 更多