微观优化是值得的时间?

我是一名PHP开发人员,我一直认为微观优化是不值得的。 如果你真的需要额外的性能,你可以编写你的软件,使其体系结构更快,或者编写一个C ++扩展来处理缓慢的任务(或者更好的是,使用HipHop编译代码)。 不过,今天一位同事告诉我,这里有很大的差别

is_array($array) 

 $array === (array) $array 

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

当你有证据表明你正在优化一个瓶颈时,微型优化是值得的。

通常这是不值得的 – 编写最可读的代码,并使用现实的基准来检查性能。 如果当你发现你已经有了一个瓶颈,那就微观地优化那些代码(随你去吧)。 有时候less量的微观优化可以产生巨大的影响。

但是不要微观地优化你所有的代码……它最终将难以维护,而且你很可能会发现你或者错过了真正的瓶颈,或者你的微观优化损害了性能,而不是帮助。

那么,对于一个非常小的数组, $array === (array) $array明显比is_array($array)快。 速度提高了7倍以上。 但是每个通话只有1.0 x 10 ^ -6秒( 0.000001 seconds )。 所以,除非你几千次这样说,否则不值得。 如果你打电话几千次,我build议你做错了什么

当你处理一个大数组时,会有所不同。 由于$array === (array) $array需要一个新的variables被复制,所以需要将这个数组在内部迭代进行比较,所以对于一个大的数组来说,它可能会显着慢一些。 例如,在一个包含100个整数元素的数组中, is_array($array)位于is_array()的一个小数组(进入10,000次迭代的0.0909秒)的误差范围内( < 2% )。 但$array = (array) $array非常慢。 只有100个元素,它已经比is_array()慢了两倍( 0.203秒)。 对于1000个元素, is_array保持不变,但演员比较增加到2.0699秒…

小数组更快的原因在于, is_array()具有作为函数调用的开销,其中cast操作是一个简单的语言结构…并且遍历一个小的variables(用C代码)通常会比函数调用开销。 但是,对于更大的variables,差异会增大…

这是一个折衷。 如果数组足够小,迭代效率会更高。 但随着数组大小的增长,它将变得越来越慢(因此函数调用将变得更快)。

另一种方式来看待它

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

先来看看is_array() 。 它的源代码基本上显示它是一个O(1)操作。 这意味着这是一个恒定的时间操作。 但是我们也需要看看函数调用。 在PHP中,具有单个数组参数的函数调用是O(1)O(n)具体取决于是否需要触发写入时复制。 如果当$array是一个variables引用时调用is_array($array) ,写时复制将被触发,并且会发生一个完整的variables副本。

因此, is_array()是最好的情况O(1)和最坏的情况O(n) 。 但只要你不使用引用,它总是O(1)

另一方面,演员版则有两个操作。 它做了一个演员,然后进行平等检查。 所以让我们分别看看。 转换操作符处理程序首先强制inputvariables的副本 。 不pipe它是否是一个参考。 所以简单地使用(array)操作符迫使O(n)遍历数组来转换(通过copy_ctor调用)。

然后,它将新的副本转换为一个数组。 对于数组和基元,这是O(1) ,但对于O(n)

然后,相同的运算符执行。 处理程序只是is_identical_function()的代理。 现在,如果$array不是数组,is_identical将会短路。 因此, 最好的情况O(1) 。 但是,如果$array 一个数组,那么如果哈希表是相同的(这意味着两个variables都是相互拷贝的副本),则它可以再次短路。 那么这种情况也是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) | +----------+-------+-----------+-----------+---------------+ 

请注意,它们看起来像缩放相同的参考。 他们没有。 它们都对参考variables进行线性缩放。 但恒定的因素改变。 例如,在大小为5的引用数组中,is_array将执行5个内存分配和5个内存副本,然后是1个types检查。 另一方面,转换版本将执行5个内存分配,5个内存副本,随后是2个types检查,接着是5个types检查和5个等式检查( memcmp()等)。 因此, n=5is_array生成11个操作数,而===(array)为22个操作数…

现在, is_array()确实具有堆栈推送(由于函数调用)的O(1)开销,但是这只会在运行时支配非常小的n值(我们在基准testing中看到只有10个数组元素是足以完全消除所有的差异)。

底线

我build议去为可读性。 我发现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"; 

编辑:为了logging,这些结果与Linux上的5.3.2 …

编辑2:修正了数组较慢的原因(这是由于迭代比较而不是内存原因)。 查看compare_function的迭代代码…

微观优化是值得的时间?

不,除非是这样。

换句话说, 先验的答案是“否”,但是你知道一个特定的代码行消耗了一个健康的时钟时间百分比,那么只有这样才值得优化。

换句话说,首先,因为否则你没有这种知识。 无论语言还是操作系统, 这都是我所依赖的方法 。

补充:当许多程序员讨论性能时,他们倾向于谈论程序花费的时间。 “那里”有一个模棱两可的含糊不清的地方, 使他们远离可以节省大部分时间的东西,即function调用站点。 毕竟,应用程序顶部的“主叫”是一个“地点”,该程序几乎从不“在”,但是要负责100%的时间。 现在,你不会摆脱“打电话给主”,但几乎总是有其他的电话,你可以摆脱。 当程序打开或closures一个文件,或将一些数据格式化成一行文本,或者等待一个套接字连接,或者“新build”一块内存,或者通过一个大型数据结构传递一个通知时,花费大量的时间来调用函数,但它是“在哪里”? 无论如何,这些电话很快就能find堆栈样本。

就像老生常谈的那样,微型优化通常只在你的代码中最小,性能最重要的热点才值得这个时间,只有在你已经certificate是瓶颈的地方之后。 不过,我想稍微补充一点,指出一些例外和误解。

  1. 这并不意味着性能不应该被认为是前所未有的。 我将微优化定义为基于编译器/解释器,硬件等的低级细节的优化。根据定义,微优化不会影响大O的复杂性。 macros观优化应该被预先考虑,特别是当它们对高层devise有重大影响的时候。 例如,可以说如果你有一个大的,经常访问的数据结构,O(N)线性search是不会削减的。 即使是只有固定的条件,但有一个很大的,明显的开销,可能是值得考虑的前期。 两个大的例子是过度的内存分配/数据复制和计算相同的东西两次,当你可以计算一次和存储/重用的结果。

  2. 如果你在稍微不同的背景下做了以前做过的事情,那么可能会遇到一些如此众所周知的瓶颈,以至于事先考虑它们是合理的。 例如,我最近正在为D标准库实现FFT(快速傅立叶变换)algorithm。 由于以前用其他语言编写过这么多的FFT,所以众所周知最大的瓶颈就是caching性能,所以我马上考虑如何进行优化。

一般来说,你不应该写任何使代码更难以理解的优化。 在我的书里,这绝对属于这个范畴。

回写旧的代码比写新的代码要困难得多,因为你必须做回归testing。 所以一般来说,没有代码已经在生产应该改变为轻浮的原因。

PHP是一种非常低效的语言,如果你遇到性能问题,你可能应该重新考虑热点,这样他们可以执行更less的PHP代码。

所以我会说在一般情况下不,在这种情况下不,并且在你绝对需要它的情况下,并且已经测量出它是可certificate的差异并且是最快的胜利(低悬的果实),是的。

当然,在现有的,正在运行的,经过testing的代码中散布这样的微观优化是一件可怕的事情,它肯定会引入回归,几乎肯定没有什么不同。

那么,我会假设is_array($array)是首选的方式, $array === (array) $array是所谓的更快的方法(这引起了一个问题,为什么不是用这个比较实现is_array ,但我离题了)。

我几乎不会回到我的代码并插入一个微优化* ,但是我会经常把它们写进代码中,只要:

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

这个特定的优化在两个方面都失败。


*确实,我确实是这样做的,但这与我有一点OCD有关,而不是良好的开发实践。

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

这里有一些比较:

is_array($v) :10秒

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

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

最后一个转换为string,数组总是被转换为值为“Array”的string。 如果$ v是一个值为“Array”的string(在我们的例子中从不发生),这个检查是错误的。

好吧,考虑到速度比速度还要快。 当你读到“更快”的select时,你是否立即想到“哦,这是检查variables是否是一个数组”,或者你认为“… wtf”?

因为真的 – 在考虑这种方法时,它多久被称为? 什么是确切的速度收益? 当数组变大或变小时,这会叠加起来吗? 没有基准就无法进行优化。

另外,如果降低代码的可读性,则不应该进行优化。 事实上,将这样的查询量减less到几十万个(这通常比人们想象的要容易得多),或者在适用的情况下优化它们会比这个微优化更有利于性能。

另外,不要像其他人所说的那样,经历这个家伙的经验,为自己思考。

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

关于Fabien Potencier ( Symfony框架的创build者) 无用的微优化的大文章 :

打印vs回声,哪一个更快?

打印使用另一个操作码,因为它实际上返回的东西。 我们可以得出结论,回声比印刷更快。 但是一个操作码没有任何成本,真的没什么。 即使脚本有数百个打印电话。 我尝试了一个新鲜的WordPress安装。 脚本在我的笔记本电脑上出现“Bus Error”错误之前停止运行,但是操作码的数量已经超过了230万。 说够了。