微观优化值得花时间吗?

时间:2010-08-12 18:54:29

标签: php performance optimization

我是一名PHP开发人员,我一直认为微观优化不值得花时间。如果你真的需要额外的性能,你可以编写你的软件,使其在架构上更快,或者你编写一个C ++扩展来处理慢速任务(或者更好的是,使用HipHop编译代码)。然而,今天一位工作伙伴告诉我,

存在很大差异
is_array($array)

$array === (array) $array

我就像“呃,这真是一个毫无意义的比较”,但他不同意我的看法......他是我们公司最好的开发人员,负责一个大约有5000万个SQL查询的网站例如,每天所以,我在这里想知道:他可能是错的还是微优化真的值得花时间和时间?

10 个答案:

答案 0 :(得分:136)

嗯,对于一个非常小的数组,$array === (array) $array明显快于is_array($array)。大约快7倍的顺序。但每次调用仅在1.0 x 10 ^ -6秒(0.000001 seconds)的顺序。因此,除非你真的称它为数千次,否则它不值得。如果你打电话数千次,我建议你做错了......

当您处理大型阵列时会出现差异。由于$array === (array) $array需要复制一个新变量,因此需要在内部迭代数组以进行比较,对于大型数组,它可能会显着变慢。例如,在包含100个整数元素的数组上,is_array($array)位于< 2%的误差范围内(is_array()),且数组较小(在0.0909秒后进入10,000次迭代)。但是$array = (array) $array非常慢。对于仅100个元素,它已经超过is_array()的两倍(在0.203秒进入)。对于1000个元素,is_array保持不变,但演员比较增加到2.0699秒......

小数组更快的原因是is_array()具有成为函数调用的开销,其中强制转换操作是一个简单的语言构造...并且迭代一个小变量(在C代码中)将通常比函数调用开销便宜。但是,对于较大的变量,差异会增加......

这是一个权衡。如果数组足够小,迭代将更有效。但随着数组大小的增加,它将变得越来越慢(因此函数调用将变得更快)。

另一种看待它的方式

另一种看待它的方法是检查每个演员的算法复杂性。

我们先来看看is_array()。它source code基本上显示它是O(1)操作。这意味着它是一个恒定的时间操作。但我们还需要查看函数调用。在PHP中,具有单个数组参数的函数调用是O(1)O(n),具体取决于是否需要触发copy-on-write。如果在is_array($array)是变量引用时调用$array,则将触发copy-on-write并将发生变量的完整副本。

因此,is_array()是最佳案例O(1)和最差案例O(n)。但只要您不使用引用,它始终是O(1) ...

另一方面,演员版本执行两项操作。它做一个演员,然后进行相等检查。那么让我们分别看一下。强制转换操作符handler首先强制输入变量a copy。无论是否参考。因此,只需使用(array)转换操作符强制对数组进行O(n)迭代以进行转换(通过copy_ctor调用)。

然后,它将新副本转换为数组。这对于数组和基元来说是O(1),对于对象来说是O(n)

然后,执行相同的运算符。 handler只是is_identical_function()的代理。现在,如果$array不是数组,则is_identical会短路。因此,它具有O(1)最佳案例。但是如果$array 一个数组,如果哈希表相同(意味着两个变量都是彼此的写时复制副本),它就会再次短路。所以这个案例也是O(1)。但请记住,我们强制上面的副本,所以如果它是一个数组我们不能这样做。所以O(n)归功于zend_hash_compare ...

所以最终结果就是这个最坏情况运行时表:

+----------+-------+-----------+-----------+---------------+
|          | array | array+ref | non-array | non-array+ref |
+----------+-------+-----------+-----------+---------------+
| is_array |  O(1) |    O(n)   |    O(1)   |     O(n)      |
+----------+-------+-----------+-----------+---------------+
| (array)  |  O(n) |    O(n)   |    O(n)   |     O(n)      |
+----------+-------+-----------+-----------+---------------+

请注意,对于引用,它们看起来相同。他们没有。它们都为引用的变量按比例缩放线性。但是不变因素会发生变化。例如,在大小为5的引用数组中,is_array将执行5个内存分配,5个内存副本,然后执行1个类型检查。另一方面,强制转换版本将执行5次内存分配,5次内存复制,然后进行2次类型检查,然后进行5次类型检查和5次相等检查(memcmp()等)。因此n=5is_array产生了11个操作,而===(array)产生了22个操作...

现在,is_array()确实具有堆栈推送的O(1)开销(由于函数调用),但是这只会在n的极小值中占主导地位(我们看到了)在上面的基准测试中,只有10个数组元素足以完全消除所有差异)。

底线

我建议去寻求可读性。我发现is_array($array)$array === (array) $array更具可读性。所以你可以充分利用两个世界。

我用于基准测试的脚本:

$elements = 1000;
$iterations = 10000;

$array = array();
for ($i = 0; $i < $elements; $i++) $array[] = $i;

$s = microtime(true);
for ($i = 0; $i < $iterations; $i++) is_array($array);
$e = microtime(true);
echo "is_array completed in " . ($e - $s) ." Seconds\n";

$s = microtime(true);
for ($i = 0; $i < $iterations; $i++) $array === (array) $array;
$e = microtime(true);
echo "Cast completed in " . ($e - $s) ." Seconds\n";

编辑:对于记录,这些结果在Linux上为5.3.2 ...

Edit2:修复了数组较慢的原因(这是由于迭代比较而非内存原因)。有关迭代代码,请参阅compare_function ...

答案 1 :(得分:80)

如果您有证据表明您正在优化瓶颈,那么微观优化是值得的。

通常不值得 - 编写最易读的代码,并使用实际的基准来检查性能。如果您发现自己遇到了瓶颈,那么微优化一下代码(随时测量)。有时,少量的微优化可以产生巨大的差异。

微优化所有你的代码......它最终会变得难以维护,你很可能会发现你已经找到了或者错过了真正的瓶颈,或者你的微观优化是伤害性能而不是帮助。

答案 2 :(得分:11)

  

微优化值得花时间吗?

不,除非是。

换句话说, a-priori ,答案是“no”,但是后你知道特定的代码行消耗了一段健康的时钟时间,那么只有这样才值得优化。

换句话说,首先是个人资料,因为否则你就没有这方面的知识。 This is the method I rely on,无论语言或操作系统如何。

补充:当许多程序员讨论性能时,他们倾向于谈论程序花费时间的“位置”。在“哪里”引导他们远离可能节省大部分时间的东西,即功能调用网站,这是一种狡猾的模糊性。毕竟,应用程序顶部的“呼叫主”是一个“地方”,该程序几乎从不“处于”,但是100%的时间负责。现在你不会摆脱“呼叫主”,但几乎总有其他呼叫,你可以摆脱。 当程序打开或关闭文件,或将某些数据格式化为一行文本,或等待套接字连接,或“新” - 一块内存,或在整个大型数据结构中传递通知时,它是在调用中花费大量时间来处理函数,但这是“它在哪里”?无论如何,这些调用很快就会发现堆栈样本。

答案 3 :(得分:4)

正如陈词滥调所说,微观优化通常只值得在代码中最小的,性能最关键的热点上进行,只有在您已经证明之后才会出现瓶颈。但是,我想稍微充实一点,指出一些例外和误解的领域。

  1. 这并不意味着不应该事先考虑性能。我将微优化定义为基于编译器/解释器,硬件等的低级细节的优化。根据定义,微优化不会影响大O复杂性。 - 优化应该被预先考虑,特别是当它们对高级设计产生重大影响时。例如,可以很自然地说,如果你有一个经常访问的大型数据结构,那么O(N)线性搜索就不会削减它。即使是那些只是常数但具有大量明显开销的事情,也可能值得考虑。两个很大的例子是过多的内存分配/数据复制,并且当你可以计算一次并存储/重用结果时计算两次相同的东西。

  2. 如果您之前在稍微不同的环境中做过某些事情,可能会出现一些众所周知的瓶颈,因此事先考虑它们是合理的。例如,我最近正致力于为D标准库实现FFT(快速傅立叶变换)算法。由于以前用其他语言写过这么多的FFT,众所周知,最大的瓶颈是缓存性能,所以我立即考虑如何优化它来进入项目。

答案 4 :(得分:3)

好吧,我将假设is_array($array)是首选方式,$array === (array) $array是所谓的更快的方式(这提出了为什么不使用is_array实现的问题那个比较,但我离题了。)

我几乎不会回到我的代码并插入微优化 * ,但我会经常在编写代码时将它们放入代码中,前提是:

  • 它不会减慢我打字速度。
  • 代码的意图仍然清晰。

特殊优化在两个方面均失败。


* 好吧,其实我这样做,但这与我有一点强迫症而不是良好的开发实践有关。

答案 5 :(得分:3)

一般情况下,您不应该编写任何使您的代码更难或更难理解的优化;在我的书中,这绝对属于这一类。

返回并更改旧代码比编写新代码要困难得多,因为您必须进行回归测试。所以一般来说,生产中的代码不应该因为无聊的原因而改变。

PHP是一种非常低效的语言,如果你遇到性能问题,你应该考虑重构热点,以便它们执行更少的PHP代码。

所以我一般都说不,并且在这种情况下没有,并且在你绝对需要它的情况下并且已经测量到它具有可证明的差异并且是最快的胜利(低悬的果实),是的。 / p>

当然,在整个现有的,经过工作和测试的代码中散布这样的微优化是一件非常糟糕的事情,它肯定会引入回归,几乎肯定不会产生明显的差异。

答案 6 :(得分:3)

我们有一个地方,优化真的很有帮助。

这里有一些比较:

is_array($v):10秒

$v === (array)$v:3,3秒

($v.'') === 'Array':2,6秒

最后一个转换为字符串,Array总是被转换为值为'Array'的字符串。如果$ v是一个值为'Array'的字符串(在我们的例子中从未发生过),则此检查将是错误的。

答案 7 :(得分:1)

嗯,除了速度之外,还有更多的事情需要考虑。当你读到“更快”的替代品时,你会立即想到“哦,这是检查变量是否是一个数组”,或者你认为“...... wtf”?

因为真的 - 在考虑这种方法时,它的频率是多少?什么是确切的速度优势?当阵列大或小时,这会堆叠吗?没有基准测试就无法进行优化。

此外,如果降低代码的可读性,则不应进行优化。实际上,将这些查询数量减少了几十万(这通常比人们想象的要容易),或者如果适用的话,优化它们会比这种微优化更有利于性能。

此外,不要像其他人所说的那样被这个人的经历所吓倒,并为自己思考。

答案 8 :(得分:1)

微优化不值得。代码可读性比微优化更重要。

很棒article about useless micro-optimization by Fabien PotencierSymfony框架的创建者):

  

print vs echo,哪一个更快?

     

Print再使用一个操作码,因为它实际上返回了一些内容。我们   可以得出结论,回声比印刷更快。但是一个操作码成本   没什么,真的没什么。即使脚本有数百个调用   打印。我试过一个新的WordPress安装。剧本   在我的笔记本电脑上以“总线错误”结束之前停止,但数字   操作码已超过230万。够了。

答案 9 :(得分:0)

IMHO微优化实际上比今天的算法优化更具相关性如果您正在一个性能关键领域工作。这可能是一个很大的 if ,因为很多人实际上并没有在性能关键领域工作,即使对于性能关键型软件也是如此,因为他们可能正在向第三方库进行高级调用实际的性能关键工作。例如,现在许多人试图编写图像或视频软件可能会在图像级别编写表达他们想要的非性能关键代码,而不必以每秒100帧以上的速度手动循环数百万像素。图书馆为他们做了这件事。

当我说微优化比今天的算法更有意义时,我并不是说,并行化的SIMD代码可以最大限度地减少应用冒泡排序的缓存未命中率,这将超过内部或基数排序。我的意思是专业人士不会对大型输入大小进行冒泡。

如果您今天采用任何合理的高级语言,其中包括C ++,您已经拥有了相当高效的通用数据结构和算法,只需触手可及。没有任何借口,除非你是一个刚刚开始学习CS的学生,只是为了将最原始的轮子重新应用于二次复杂性分类到大规模输入大小或线性时间搜索,这可以通过适当的数据在恒定时间内完成结构。

因此,一旦超过这个初学者级别,性能关键型应用程序的性能特征仍然会有很大变化。为什么?当开发人员在算法上没有做任何非常愚蠢的事情时,为什么一个视频处理软件的帧速率和交互式视频预览的三倍?为什么一个服务器做一个非常相似的事情能够用相同的硬件处理十倍的查询?为什么这个软件会在5秒内加载一个场景而另一个加载5分钟加载相同的数据?为什么这款精美的游戏具有丝般流畅和一致的帧速率,而另一款游戏的图形和灯光更加丑陋,更具原始外观,并且在拍摄两倍内存时会出现断断续续的情况?

这归结为微优化,而不是算法差异。此外,我们今天的内存层次结构在性能方面存在偏差,使得之前几十年前被认为是好的算法如果表现出较差的参考局部性就不再那么好了。

因此,如果你想在今天编写具有竞争力的高效软件,那么通常会归结为多线程,SIMD,GPU,GPGPU,通过更好的内存访问模式改善引用的局部性(循环平铺,SoA) ,热/冷场分裂等,甚至可能在极端情况下优化分支预测,等等,除非你正在处理一个以前没有程序员冒险过的极其未开发的领域,否则不是算法的突破。

有时算法突破仍然是潜在的游戏改变者,比如最近的体素锥追踪。但这些都是例外,而那些提出这些的人往往将他们的生命投入到研发中(他们通常不是人们编写和维护大规模代码库),并且它仍然归结为微观优化,无论体素锥跟踪是否可以适用于游戏等实时环境。如果你不擅长微优化,即使使用这些算法突破,你也无法获得足够的帧速率。