微观优化值得花时间吗?

Tow*_*wer 37 php optimization performance

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

is_array($array)
Run Code Online (Sandbox Code Playgroud)

$array === (array) $array
Run Code Online (Sandbox Code Playgroud)

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

irc*_*ell 138

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

当你处理一个大型数组时会出现差异.由于$array === (array) $array需要复制一个新变量,因此需要在内部迭代数组进行比较,对于大型数组来说,它可能会显着变慢.例如,在具有100个整数元素的数组上,使用小数组(在几秒钟内进行10,000次迭代)is_array($array)的error(< 2%)范围内.但是非常慢.对于只有100个元素,它已经超过两倍(在几秒钟内进入).对于1000个元素,保持不变,但演员比较增加到秒......is_array()0.0909$array = (array) $arrayis_array()0.203is_array2.0699

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

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

另一种看待它的方式

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

我们先来看一看is_array().它的源代码基本上表明它是一个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)......

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

然后,它将新副本转换为数组.这适用O(1)于数组和基元,但O(n)适用于对象.

然后,执行相同的运算符.该处理器只是一个代理的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)      |
+----------+-------+-----------+-----------+---------------+
Run Code Online (Sandbox Code Playgroud)

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

现在,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";
Run Code Online (Sandbox Code Playgroud)

编辑:为了记录,这些结果是在Linux上的5.3.2 ...

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

  • +1.我想"最好的程序员"应该肯定会提供你的答案和基准代码片段. (8认同)

Jon*_*eet 81

当您有证据表明您正在优化瓶颈时,微优化是值得的.

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

但是不要微观优化你的所有代码......它最终会变得难以维护,你很可能会发现你要么错过了真正的瓶颈,要么你的微观优化正在损害性能而不是帮助.

  • 哦不,他也是为了获得PHP徽章! (22认同)
  • @Jon - 你能回去写关于我喜欢阅读的C#等书籍,并将这些低调的果实留给我们,仅仅是凡人吗? (6认同)
  • 完全同意.值得的时间吗?你会知道你的应用程序,因为你可以分析,你会看到优化将带来足够的好处.要回答另一个问题,"你应该微观优化一切吗?" 绝对不.在大多数情况下,清晰读取和维护的代码比对性能不敏感的快速代码更为重要.编程就是妥协与平衡.代码复杂性,可维护性,性能,程序员时间成本,硬件要求 - 成本.大多数情况下,开发时间和错误比硬件要昂贵得多. (3认同)
  • @Jon - 你叫一个205K的SO代表偶尔**分心????? 但我很期待阅读你的下一本书.最初的C#深度让我想起了Scott Meyers的C++书籍,我非常喜欢这些书籍. (2认同)

Mik*_*vey 11

微观优化值得花时间吗?

不,除非是.

换句话说,先验,答案是"不",但您知道特定的代码行消耗了健康的时钟时间百分比之后,那么只有这样才值得优化.

换句话说,首先描述,否则你没有那种知识.这是我依赖的方法,无论语言或操作系统如何.

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


dsi*_*cha 6

正如陈词滥调,只有在您证明这是瓶颈所在之后,微优化通常才值得在代码的最小、最关键的性能热点上花费时间。但是,我想稍微充实一下,指出一些例外情况和误解领域。

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

  2. 如果您正在做一些以前在略有不同的上下文中做过的事情,那么可能存在一些众所周知的瓶颈,因此提前考虑它们是合理的。例如,我最近正在研究 D 标准库的 FFT(快速傅立叶变换)算法的实现。由于之前用其他语言编写了这么多FFT,众所周知最大的瓶颈是缓存性能,因此我立即进入项目考虑如何优化它。