微优化值得花时间吗?
微观优化是否值得时间?
不值得,除非是值得的。
换句话说,事先的答案是“不”,但只有在你知道特定的代码行消耗了很大的时钟时间之后,才值得进行优化。
换句话说,首先进行性能分析,因为否则你就没有这个知识。无论使用什么语言或操作系统,这是我依赖的方法。
补充:当许多程序员讨论性能时,从专家到初学者,他们往往会谈论程序花费时间的“位置”。这个“位置”中有一个隐蔽的模糊性,它让他们远离可能节省最多时间的事物,也就是函数调用点。毕竟,在应用程序顶部的“call Main”几乎从来不是程序所在的“位置”,但却负责了100%的时间。现在你无法摆脱“call Main”,但几乎总会有其他你可以摆脱的调用。
当程序打开或关闭文件、将某些数据格式化为一行文本、等待套接字连接、创建一块内存或在一个大型数据结构中传递通知时,它在调用函数中花费了大量的时间,但那是它所在的“位置”吗?无论如何,这些调用可以通过堆栈采样快速找到。
关于“通过堆栈采样快速找到”:是的,但很少有人知道,尽管它非常简单(也许这就是问题所在)。例如,通常可以在Visual Studio中完成,而无需其他工具。
:这是一个教育问题。40年前,我是一名教授,我亲眼见证了这个问题。教授们很少接触真正庞大的代码-他们只关注算法和大O符号,这在某种程度上是可以接受的。当课程提到“谈论性能分析”时,他们会谈论gprof
及其现代版本。完成这个任务后,他们就会继续下一个任务。学生们会做什么呢?在课堂上,他们的思维是开放的,可以接受智慧的灌输。然后当他们毕业时,他们认为角色发生了逆转-他们的工作是教育我们这些古董他们所学到的东西。我相信你也见过这种情况。 🙂
微优化是否值得花时间?
当你有证据表明你正在优化一个瓶颈时,微优化是值得的。
通常情况下,这是不值得的-编写尽可能易读的代码,并使用实际的基准测试来检查性能。如果你发现自己有一个瓶颈,只需对那部分代码进行微优化(同时进行测量)。有时微小的优化可以带来巨大的差异。
但是不要对所有代码进行微优化...这样会更难维护,并且你很可能会发现你要么错过了真正的瓶颈,要么你的微优化会损害性能而不是帮助。
完全同意。这值得花时间吗?你可以通过分析来了解你的应用程序,从而确定优化会带来足够的好处。回答另一个问题,“你应该对所有代码进行微优化吗?”绝对不。在大多数情况下,清晰易读且易于维护的代码比快速但不敏感于性能的代码更重要。编程是关于妥协和平衡的。代码复杂性、可维护性、性能、程序员时间成本、硬件要求成本。大多数情况下,开发时间和错误比硬件成本更昂贵。
- 你能回到写关于C#等的书,让我们这些凡人来做低级别的工作吗?
:如果这能安慰你,我目前正在审阅《C# in Depth》的第14章的证据。SO只是偶尔的干扰 🙂
哦不,他也准备获得PHP徽章!
- 你把SO声望205K称为偶尔的干扰吗???但我期待着阅读你的下一本书。《C# in Depth》的原版让我想起了Scott Meyers的C++书籍,我非常喜欢。
:如果你在书中提到了性能分析,我希望你能考虑到这些问题:stackoverflow.com/questions/1777556/…
:不,关于性能分析,我在书中几乎没有提到。
微优化是否值得时间?
微优化是指对代码进行微小的改进,以提升性能。本文讨论了一个微优化的案例,即在判断一个变量是否为数组时,使用$array === (array) $array
比使用is_array($array)
更快。作者指出,对于一个非常小的数组来说,前者比后者快7倍以上。但每个调用的时间只有1.0 x 10 ^ -6
秒(0.000001秒
),所以除非需要调用非常多次,否则不值得进行微优化。对于大数组而言,情况就不同了。因为$array === (array) $array
需要复制一个新变量,并且需要在比较时对数组进行迭代,所以对于大数组来说,它会明显慢得多。举例来说,对于包含100个整数元素的数组,is_array($array)
和小数组时的is_array()
相差不大(误差小于2%),耗时为0.0909
秒(10,000次迭代)。但$array = (array) $array
非常慢,对于只有100个元素的数组来说,它已经比is_array()
慢两倍以上(耗时为0.203
秒)。对于1,000个元素的数组来说,is_array
的耗时保持不变,而强制转换的比较时间增加到了2.0699
秒。
这个微优化之所以对小数组有效,是因为is_array()
调用的开销比较大,而(array)
转换操作是一个简单的语言结构。在C代码中,迭代一个小变量的代价通常比函数调用的开销小。但是对于更大的变量,差异会变得更大。
这是一个权衡。如果数组足够小,迭代会更高效。但随着数组的增大,迭代会变得越来越慢(因此函数调用会变得更快)。
另一种看待这个问题的方式是分析每种转换的算法复杂度。首先看is_array()
。它的源代码显示它是一个O(1)
的操作,也就是说它的时间复杂度是常数。但我们还需要考虑函数调用。在PHP中,带有单个数组参数的函数调用的时间复杂度要么是O(1)
,要么是O(n)
,这取决于是否需要触发写时复制。如果在$array
是一个变量引用时调用is_array($array)
,将会触发写时复制,并且会进行一次完整的变量复制。
因此is_array()
的最优情况是O(1)
,最坏情况是O(n)
。但只要不使用引用,它始终是O(1)
。
另一方面,强制转换版本执行两个操作。它先进行类型转换,然后进行相等性检查。我们分别看一下这两个操作。类型转换运算符首先强制进行输入变量的复制,无论它是引用还是不是。因此,简单地使用(array)
转换操作会导致对数组进行O(n)
的迭代(通过复制构造函数调用)。
然后,它将新的副本转换为数组。对于数组和基本类型来说,这是O(1)
,但对于对象来说,它是O(n)
。
然后,执行相等性运算符。这个操作只是一个代理到is_identical_function()
的操作。现在,如果$array
不是一个数组,is_identical
会进行短路。因此,它的最优情况是O(1)
。但如果$array
是一个数组,它可以再次进行短路,如果哈希表是相同的(意味着两个变量都是彼此的写时复制副本)。所以这种情况也是O(1)
。但请记住,我们在上面强制复制了一个副本,所以如果它是一个数组,我们就不能这样做。所以由于zend_hash_compare
,它是O(n)
...
因此,最终的结果是最坏情况下的运行时间表:
+----------+-------+-----------+-----------+---------------+ | | 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=5
时,is_array需要11个操作,而===(array)
需要22个操作...
我建议选择可读性更好的方法。我认为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版本得出的。
编辑:为了记录,这些结果是在Linux上使用5.3.2版本得到的。
编辑2:修正了数组比较较慢的原因(这是由于迭代比较而不是内存原因)。可以在compare_function中查看迭代代码...
最后,作者建议选择可读性更好的方法。他认为is_array($array)
比$array === (array) $array
更易读。所以你可以兼顾两者。