前言
伴随着互联网的快速发展,数据的规模及软件的复杂程度呈指数级增长,带来的硬件资源开销、能源消耗也越来越大。为了控制硬件成本,也为了支持更大的并发量,软件性能的优化工作也越来越重要。接下来,本文将从“充分了解 CPU”、“深入理解编译器”、“对你的代码负责”三个方面详细、全面介绍下软件性能优化的原理及相关方法。文章最后给出总结:性能优化的时机、性能优化的注意事项及性能优化需要的工具有哪些。
1 充分了解 CPU
系统的 CPU 理论知识太多、太复杂,包括 CPU 架构、运算单元、记忆单元、控制单元、数据总线、指令周期、多核、超线程等等,这里只讲三个性能优化时常见的概念:cache、寄存器和多线程。
- cache
关于 cache,你只要记住一点:CPU 在执行任务的时候,从 cache 读取数据的速度远远大于从内存读取数据的速度。而程序在执行时,会有数据 cache miss 和指令 cache miss。cache 的容量决定了有多少代码和数据可以放到 cache里面,我们优化程序的目标是把程序(代码段)尽可能放到 cache 里面,读取数据时,尽可能做到 cache 里的数据都是要用的。涉及的手段有:
结构体变量字节对齐
预取(prefetch):提前获取下一阶段程序执行需要的数据
函数重排:获取程序运行轨迹,重排二进制目标文件(elf 文件)里的代码段
函数冷热分区
- 寄存器
控制函数入参个数(对有的硬件而言,当函数入参不超过 4 个时,入参是放在寄存器里的,寄存器读取速度那是相当快滴)。
- 多线程
多线程是充分利用 CPU 多核优势,避免 I/O 时 CPU 闲置。这里一个重要的性能瓶颈点是:锁。如何定位锁的开销?这里可以大致给你个数据,一般单次加解锁 100 cycles,spinlock 或者 cas 更快点。多线程场景下,如果 CPU 利用率上不去,而系统吞吐量也上不去,那么大概率时锁导致的性能下降。
2 深入理解编译器
避免过多的函数跳转,请使用内联函数和函数宏;
编译器自带的优化项,比如 gcc -O3 等;
32 位环境里面用 64 位 counter 很显然会影响性能,所以除非必要,最好别用;
使用静态内存替代堆内存,,可以做到提前分配;
利用编译器自带的优化工具,做好分支预测。
3 对你的每一行代码负责
算法的重要性相必大家都很清楚了,每个人都能想到这一点,好的算法能带来性能数量级上提升。这里,推荐一种最简单也是最有效的手段:查表,内存足够的话,数组性能最高。
不要做额外的事情,特别是无用的事情,避免重复计算。
内存池能大量减少内存碎片。
理清多条件判断的顺序,尽量在最外层条件判断时就能有结果。
除了上述三点之外,还有 I/O 优化、网络通信时间优化、硬件加速(使用专有硬件,将部分软件功能下沉到硬件)、优化代码流程等方法。
总结:
- 性能优化的时机
当你遇到性能瓶颈时,你就不得不做性能优化了。在一些软件竞赛时,特别明显,除了比拼算法(软件大赛题目一般都是 NP-hard 问题,主要看谁的程序模拟的轮次更多、更有效),剩下的就是要如何榨干 CPU 的每一个 TICK。
- 性能优化的工具
善用 perf(linux 自带的工具,采集程序运行时的线程、函数等开销)及生成火焰图(图形显示函数调用栈及 CPU 占用率)。具体的使用方法,各位同学自行学习哈,还有 top -H、ps -aux 等常用命令。
- 性能优化的注意事项
性能优化时,不能破坏原有的程序功能,更不能引入新的 bug。
要平衡好程序性能和代码美观度及可读性之间的关系。
性能优化要按照软件重构的步骤:
一个时刻只戴一顶帽子;可观察行为保持;小步前进
最后,引用大神 Kent Beck 的一句话作为文章的结尾:“你可能不会成为一个伟大的程序员,但是你可以成为一个具有伟大习惯的好程序员。”