关于php:微优化值得吗?

Is micro-optimization worth the time?

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

1
is_array($array)

1
$array === (array) $array

我说"嗯,这真是一个毫无意义的比较",但他不同意我的看法。他是我们公司最好的开发人员,负责一个每天执行大约5000万次SQL查询的网站——例如。所以,我想知道:他是错了,还是微观优化真的值得花时间和时间?


对于一个非常小的阵列,$array === (array) $arrayis_array($array)快得多。速度快7倍以上。但每次通话都只按1.0 x 10 ^ -6秒(0.000001 seconds秒)的顺序进行。所以,除非你真的这么称呼它,否则它是不值得的。如果你无数次这样称呼它,我建议你做了错事…好的。

当你处理一个大的数组时,差异就出现了。由于$array === (array) $array要求复制一个新的变量,需要对数组进行内部迭代以进行比较,因此对于大型数组来说,它可能会慢得多。例如,在具有100个整数元素的数组上,is_array($array)is_array()的误差范围内(< 2%内),使用一个小数组(在0.0909秒处输入10000次迭代)。但是$array = (array) $array非常慢。对于只有100个元素,它的速度已经是is_array()的两倍多(在0.203秒时进入)。对于1000个元素,is_array保持不变,但铸造比较增加到2.0699秒…好的。

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

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

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

我们先来看看is_array()。它的源代码基本上表明它是一个O(1)操作。这意味着它是一个固定时间的操作。但是我们也需要看看函数调用。在PHP中,具有单个数组参数的函数调用可以是O(1)O(n),这取决于是否需要触发copy-on-write。当$array为变量引用时,如果调用is_array($array),将触发写拷贝,并生成变量的完整副本。好的。

因此,is_array()是最好的O(1)和最坏的O(n)两种情况。但只要你不使用参考文献,它总是O(1)…好的。

另一方面,Cast版本执行两个操作。它执行强制转换,然后执行相等检查。让我们分别看一下。强制转换运算符处理程序首先强制复制输入变量。不管是不是推荐信。因此,只需使用(array)casting操作符,就可以强制在数组上执行O(n)迭代来强制转换(通过copy-ctor调用)。好的。

然后,它将新副本转换为数组。这是用于数组和原语的O(1),而用于对象的O(n)。好的。

然后,执行相同的运算符。这个处理程序只是is_identical_function()的代理。现在,如果$array不是一个数组,是否会发生同样的短路。因此,最好是使用O(1)。但是如果$array是一个数组,那么如果哈希表是相同的(这意味着两个变量都是在对方的写副本上复制的),它就可以再次短路。所以这个例子也是O(1)。但请记住,我们在上面强制复制了一个副本,所以如果它是一个数组,我们就不能这样做。所以是O(n),感谢Zend_Hash_比较…好的。

因此,最终结果是最坏情况下的运行时表:好的。

1
2
3
4
5
6
7
+----------+-------+-----------+-----------+---------------+
|          | 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_数组将执行5个内存分配和5个内存副本,然后执行1个类型检查。另一方面,Cast版本将执行5个内存分配、5个内存副本、2个类型检查、5个类型检查和5个相等检查(memcmp()或类似)。因此,n=5产生11个ops的is_array,而===(array)产生22个ops的n=5。好的。(P)Now,EDOCX1 penographical 0 does have the O(1)overhead of a stack push(due to the function call),but that'll only dominate runtime for extremely small values of EDOCX1 individual 1(we saw in the benchmark above just 10 array elements was enough to completely eliminate a ll difference).好的,好的。Bottom Line(P)我已经开始准备好思考了I find EDOCX1 English 2 to be far more readable than three EDOCX1.So you get the best of both worlds.好的,好的。(P)The Script I used for the Benchmark:好的,好的。字母名称(P)Edit:For the record,these results were with 5.3.2 on Linux…好的,好的。(P)Edited 2:Fixed the reason the array is slower(it's due to the iterated comparison instead of memory reason s).See compare?uu function for the iteration code…好的,好的。好吧。


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

通常情况下,这是不值得的——编写最可读的代码,并使用实际的基准来检查性能。如果你发现你遇到了瓶颈,那么就对那部分代码进行微观优化(随时随地测量)。有时,少量的微观优化会产生巨大的差异。

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


Is micro-optimization worth the time?

不,除非是。

换句话说,a-priori,答案是"否",但是当你知道一行特定的代码消耗了百分之健康的时钟时间后,只有这样才是值得优化的。

换言之,先分析一下,因为否则你就没有这些知识。这是我所依赖的方法,不管是语言还是操作系统。

补充:当许多程序员讨论性能时,从专家到下,他们倾向于讨论程序在哪里花费时间。在"在哪里"中有一种潜移默化的模糊性,它使他们远离那些可以节省最多时间的东西,即函数调用站点。毕竟,应用程序顶部的"call-main"是一个程序几乎从未"at"过的"place",但它负责100%的时间。现在你不打算摆脱"主叫",但几乎总是有其他的电话你可以摆脱。当程序打开或关闭一个文件,或将某些数据格式化为一行文本,或等待套接字连接,或"新建"一块内存,或在整个大型数据结构中传递通知时,它在调用函数时花费了大量时间,但这是"在哪里"吗?不管怎样,这些调用很快就会在堆栈示例中找到。


正如陈词滥调所说,只有在证明了瓶颈所在之后,才有必要在代码中最小、性能最关键的热点上花时间进行微优化。不过,我想把这一点充实一下,指出一些例外和误解的地方。

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

  • 如果你所做的事情是在一个稍有不同的背景下完成的,那么可能存在一些众所周知的瓶颈,因此提前考虑它们是合理的。例如,我最近正在为D标准库研究一种快速傅立叶变换算法的实现。由于以前用其他语言编写过如此多的FFT,众所周知最大的瓶颈是缓存性能,所以我立即进入项目,考虑如何优化它。


  • 我们有一个地方,在那里优化是非常有帮助的。

    这里比较一下:

    is_array($v)10秒

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

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

    最后一个类型是强制转换为字符串,数组总是强制转换为值为"array"的字符串。如果$v是一个值为"array"的字符串(在我们的例子中从未发生过),则此检查将是错误的。


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

    与编写新代码相比,返回并更改旧代码要困难得多,因为您必须进行回归测试。因此,一般来说,不应该因为琐碎的原因而更改已经在生产中的代码。

    PHP是一种非常低效的语言,如果您有性能问题,您可能需要重构热点,这样它们就可以执行更少的PHP代码。

    所以我一般说不,在这种情况下,不,在你绝对需要它的情况下,并且已经测量到它有一个可证明的区别,是最快的胜利(低挂水果),是的。

    当然,在现有的、工作的、测试过的代码中分散这样的微观优化是一件可怕的事情,它肯定会引入回归,几乎肯定不会产生明显的差异。


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

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

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

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

    >好的,事实上我是这样做的,但这与我接触OCD而不是良好的开发实践有更多的关系。


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

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

    print vs echo, which one is faster?

    Print uses one more opcode because it actually returns something. We
    can conclude that echo is faster than print. But one opcode costs
    nothing, really nothing. Even if a script have hundreds of calls to
    print. I have tried on a fresh WordPress installation. The script
    halts before it ends with a"Bus Error" on my laptop, but the number
    of opcodes was already at more than 2.3 millions. Enough said.


    好吧,考虑的东西比速度还多。当你读到"更快"的选项时,你会立即想到"哦,这是在检查变量是否是一个数组",还是你会想到"…wtf"?

    因为真的-当考虑这个方法时,它被调用的频率是多少?准确的速度优势是什么?当数组较大或较小时,这会叠加吗?没有基准就不能进行优化。

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

    另外,不要像别人所说的那样被这家伙的经历吓倒,要为自己着想。


    如果您在性能关键领域工作,那么imho微优化实际上比目前的算法优化更具相关性。这可能是一个很大的假设,因为许多人实际上并不在性能关键领域工作,甚至对于性能关键的软件也是如此,因为他们可能只是在第三方库中进行高层调用,而第三方库完成了实际的性能关键工作。例如,现在很多人试图写一个图像或视频软件,可能会写一些非性能关键的代码来表达他们在图像级别的需求,而不必手动以每秒100+帧的速度循环通过几百万个像素。图书馆为他们做这些。

    当我说现在的微优化比算法优化更重要时,我的意思并不是说,并行化的simd代码可以最大限度地减少缓存未命中,应用冒泡排序将击败内向排序或基数排序。我的意思是专业人士不会对大输入量进行冒泡排序。

    如果你今天使用任何合理的高级语言,我包括C++,那么你就可以在你的指尖中得到你的合理有效的通用数据结构和算法。没有任何理由,除非你是一个刚开始学CS的学生,只是把你的脚弄湿,重新设计最原始的车轮,将二次复杂度排序应用于大规模输入量或线性时间搜索,这些搜索可以在恒定时间内用适当的数据结构完成。

    因此,一旦您通过了这个初级级别,性能关键型应用程序仍然具有各种各样的性能特征。为什么?为什么一个视频处理软件的帧速率是另一个软件的三倍,而且在开发人员没有做任何非常愚蠢的算法时,交互视频预览会比另一个软件多?为什么一个执行类似操作的服务器能够用相同的硬件处理十倍的查询?为什么这个软件在5秒钟内加载一个场景,而另一个软件在5分钟内加载相同的数据?为什么这个漂亮的游戏有丝般光滑和一致的帧速率,而另一个更丑,更原始的外观与它的图形和照明,以及口吃这里和那里,而采取两倍的记忆?

    这可以归结为微观优化,而不是算法上的差异。此外,我们今天的内存层次结构在性能上是如此的倾斜,使得在几十年前被认为是好的以前的算法如果表现出糟糕的引用位置,就不再像以前那样好了。

    因此,如果您今天想编写具有竞争力的高效软件,那么通常情况下,这将归结为多线程、SIMD、GPU、GPGPGPU、使用更好的内存访问模式(循环平铺、SOA、热/冷字段拆分等)改善引用位置、甚至在极端情况下优化分支预测等等。H,没有那么多算法上的突破,除非你正在处理一个非常未被开发的领域,在那里以前没有程序员冒险过。

    偶尔也会有算法上的突破,这些突破可能会改变游戏规则,比如最近的体素锥体跟踪。但这些都是例外,提出这些问题的人往往将他们的生命投入到研发上(他们通常不是编写和维护大规模代码库的人),而且归根结底还是微观优化,不管体素锥体跟踪是否可以应用于游戏等实时环境。如果你不擅长微优化,即使使用这些算法突破,你也无法获得足够的帧速率。