前言
最近为了锻炼自己的耐心、提高知识水平,也顺便推进英语学习计划,我打算读几本古法编程书籍。
今天阅读了《C Interfaces and Implementations》的 1.3 小节。虽然这本书已经出版几十年了,但里面的道理放到现在还是很有用。于是我结合之前实习时的经历,写一下对“效率提高”这件事情的看法。
什么时候才需要提高效率?
很多程序员想当然地觉得某个地方效率不高,于是花费大量时间进行改进,但这些改进往往没有什么用。程序到底快不快,不是“两眼一瞪”就能看出来的,而要通过实际测量判断。
所谓“太慢”也得看需求:到底是响应慢,还是吞吐量、并发能力或资源占用没有达标?如果测量结果没有说明性能不够,就算某段代码看起来不够“快”,也不要只为了性能去改它。至于正确性、安全性和可维护性,那是另外一回事。
即使效率真的提高了,也要考虑它是否带来了很大的复杂度。很多时候,程序足够快比一味追求最快更好。针对某个地方做的专门优化越多,后续越容易出现 Bug,维护者接手这部分代码或增加新功能时也会更加困难。
可靠性和正确性比单纯追求速度更重要。一个程序即使很快,但经常崩溃或产生错误结果,也没有什么用。足够快,并且能够稳定、正确地完成任务就行了。
优先提高哪里的效率?
找出瓶颈的唯一方法就是度量程序的各个环节。
测完以后,先改最拖后腿、又确实能改的地方。假设工作负载和其他部分没有明显变化,如果某个环节只占很少时间,即使把它优化得再快,对整体性能的帮助也很有限。
比如我之前处理过一个实时翻译卡顿的问题,最后发现就是 I/O 读写跟不上,不是翻译阶段太慢。那就先处理 I/O,没必要在没有证据的情况下,先去折腾网络和翻译 API。
还是那个道理,足够快就行了,并非所有地方都要优化。
怎么优化?
如果性能问题来自整体设计,局部微调通常救不了它。如果程序到处都慢,或者很多地方都卡在同一个设计问题上,就应该先检查算法、数据流和整体设计,而不是继续在某个循环里挤出几条指令。
对于某个部分,直接换用更合适的算法,通常比在原有算法上缝缝补补更有效。例如,对于已经有序、需要反复查询的数组或其他适合随机访问的序列,二分查找通常比线性查找更合适。
当然,实际项目中大多数时候只能硬着头皮去改,这也是没有办法的事情。这时只能尽量少改,先解决主要瓶颈;没有权限修改的地方就不要动。
改完以后,还要在相同条件下再测一遍,并执行正确性和回归测试。否则得到的可能只是一个更漂亮的数字,实际问题却没有解决,甚至还用可靠性换来了速度。
结语
在性能优化这件事情上,结论应该尽量建立在明确的目标、可复现的测量,以及修改前后的对比上,不能只靠“我寻思”就去做某件事情。
为了时间、金钱以及程序员的头发,程序能够足够快地稳定完成任务就行了。盲目追求最快往往没有意义。当然,如果你是参与 FFmpeg 之类项目的大佬,手搓汇编什么的,当我没说。
参考资料
- David R. Hanson:《C Interfaces and Implementations》1.3 节