性能优化这个话题可大可小。从大的方面来说,在系统设计之初,需要考虑硬件的选择,操作系统的选择,基础软件平台的选择;从小到方面来说,每个子系统的设计,算法选择,代码怎么写,如何使用编译器的选项,如何发挥硬件的***性能等等。本系列关注的是在给定硬件平台,给定操作系统和基础软件平台的情况下,如何优化代码,简而言之,就是关注小的方面。
在给定硬件平台的情况下,如何发挥硬件的***效能?前提是需要对硬件平台很熟悉。如何分离硬件相关代码和硬件无关的代码是一个很重要的技能。针对某一硬件平台优化固然是好,但是如果代码都是硬件相关的,就失去了可移植性,软件的性价比不一定高。
性能优化只是系统的一个方面,它会和系统的其他要求有冲突,比如
- 可读性:性能优化不能影响可读性,看起来不怎么漂亮的代码,没有人愿意维护
- 模块化:性能优化往往需要打破模块的边界,想想这是否值得
- 可移植:隔离硬件相关的代码,尽量使用统一的API
- 可维护:许多性能优化的技巧,会导致后来维护代码的人崩溃
需要在性能优化和上述的几个要求之间做出tradeoff,不能一意孤行。
性能优化的目标是什么?不外乎两个:
- 时间性能:减小系统执行的时间
- 空间性能:减小系统占用的空间
那么我们优化的策略,首先就是对系统进行分解,测量:
- 分解:系统包含的子系统,执行路径,函数,指令等等
- 测量:每个子系统,执行路径,函数,指令所花费的时间和空间
然后,选取执行次数最多,消耗时间最长的函数进行优化(这里需要指出的是,消耗时间最长的函数并不一定就是优化的对象,这个需要放到某个执行路径上去考察,优化最常使用的执行路径)。
对于单核和多核的优化,有什么不同?以cache miss为例,在单核上
- I-cache miss的原因是什么?一是代码路径太长,以至于超出了cache的容量,cache需要替换;二是 多个执行路径之间相互交替,cache需要替换;三是I-cache, D-cache互相踩。(对于i-cache的优化,基本上没有什么可遵循的规则。最有效的还是:一,减少无用的代码;二,减少冗余的代码;三,减少函数调用的开销;4,快速路径短小精干,相关代码相邻;5,相关代码放到一个段里面等等)
- D-cache miss的原因是什么?一是数据结构太大,超出了cache的容量(大数组,长链表都会有这个问题,比较之下,还是数组更让人放心一点);二是多个数据结构之间相互踩(问题是一个执行路径,需要访问那么多数据吗?如果是,这个路径是不是太复杂了一点);三是D-cache和I-cache的冲突(这个不好说,系统里面会出现这种模式的cache miss吗?)
在多核上,cache miss会有什么新的特点?
- I-cache miss:多核有独立的L1 cache,共享的L2/L3 cache。如果多条执行路径同时进行,cache的冲突几率就会增大。但是这是没办法的事情,总不能什么事都不做吧。所以,对i-cache的优化,关注快速路径就可以了,不要把精力都花费在这个上面
- D-cache miss:锁,原子操作,伪共享等都会引起多核上的D-cache miss。这是多核优化时需要关注的重点。优化的策略也很直接,就是尽量减少锁,原子操作,伪共享等。多核的所有冲突都是由共享引起的,所以要区分出哪些是必须共享的,哪些是可以per thread的。并行优化与应用有关,需要注意的是,优化是否对所有用例都有效?做不好,可能顾此失彼。
性能优化必备技能
- 熟悉系统执行路径:可以通过读代码或者使用profiling工具学习代码,要思考执行路径上不合理的地方,看看哪里可以减少,哪里可以合并。熟悉系统执行路径是性能优化的基础。
- 熟悉测量工具:顺手的工具必不可少
- 常用的代码优化技巧和策略:针对不同的语言,不同的平台,使用与之相应的技巧和策略
学习性能优化是一个不断积累的过程,在这个过程中,总结和学习自己用的顺手的工具和技巧,不断尝试,不断思考,就会有收获。
【编辑推荐】