#0 系列目录#
性能优化的目标是什么?不外乎两个:
时间性能
:减小系统执行的时间空间性能
:减小系统占用的空间
#1 性能估算# 给定一个问题,往往会有多种设计方案,而方案评估的一个重要指标就是性能
,如何在系统设计时估算
而不是在程序执行时测试得到性能
数据是系统架构设计的重要技能
。性能估算有如下用途:
-
多种设计方案选择;
-
评价程序实现是否足够优化;
-
向框架/服务提供方提出性能要求的依据;
通过查看程序运行时CPU及网络的使用情况来评价程序是否足够优化,这也是一种很重要的方法。然而,这种方法掩盖了不优化的实现,如O(N)的算法被错误实现成O(N^2),网络收发冗余数据等。
性能评估需要假设程序的执行环境,如集群规模及机器配置,集群上其它服务占用资源的比例。首先,我们需要知道一些常见硬件的大致性能参数:
L1 cache reference 0.5ns CPU一级缓存
Branch mispredict 5ns 分支预测
L2 cache reference 7ns CPU二级缓存
Mutex lock/unlock 100ns
Main memory reference 100ns 内存
Send 1M bytes over 1Gbps network 10ms 1G的宽带传输1MB
Read 1M sequentially from memory 0.25ms 内存读取1MB数据
Round trip within data center 0.5ms
Disk seek 8~10ms 磁盘寻道
Read 1MB sequentially from disk 20~25ms 磁盘读取1MB数据
其中,磁盘的性能指标专指分布式平台专用的大容量磁盘,寻道时间为8~10ms,顺序读取速率为40~50MB。某些产品线使用SCSI磁盘或者Flash盘,性能较好,评估时需查看硬件的性能参数。磁盘和网络都有一个特征,一次读写的数据量越大性能越好
,这是由硬件特征及底层软件算法决定的,如tcp慢连接和磁盘寻道时间长。
#2 代码优化# 做代码优化前,先了解下硬件Cache:
- Cache Level:通常来说
L1、L2的Cache集成在CPU里,L3的Cache放在CPU外
; - Cache Size:它决定你
能把多少东西放到Cache里
,有Size就有竞争,就有替换,才有所谓优化的空间; - Cache Type:I-Cache(指令),D-Cache(数据),TLB(MMU的Cache);
代码层次的优化主要从以下两个角度考虑问题:
- I-Cache优化:精简code path,
简化调用关系,减少冗余代码
等等; - D-Cache优化:减少D-Cache的miss数量,
增加有效数据访问
;
以下是一些技巧,可供参考:
- Code adjacency(
把相关代码放在一起
)。
这里有两层含义:一是相关源文件要放在一起;二是相关函数在object文件里面,也应该相邻。这样,可执行文件被夹在到内存里时,函数位置也是相邻的,同事还符合模块化编程的要求:高内聚,低耦合
。
- Cache line alignment(
cache对齐
)。
对齐Cache以减少潜在的一次读写,但这可能意味着内存的浪费,需要从空间和时间两方面衡量
。
- Branch prediction(
分支预测
)。
如果能预测那段代码有更高的执行概率,就能减少跳转次数
(调整if和else的顺序?)。
- Data prefetch(数据预取)。
由CPU自动完成。
-
Register parameters(寄存器参数)。
-
Lazy computation(延时计算)。
最近不用的变量,不要急着去初始化(意味着可能执行复杂的构造),如果某个分支跳出了函数,这些动作就浪费了。COW(copy-on-write)就是一种延时计算的技术
。
- Early computation(提前计算)。
有些变量,计算一次就够了,任何加减乘除都会消耗CPU指令,尽量使用常数,而不是246060来表示一天的秒数。
-
Inline(内联函数)。
-
Macro(宏定义)。
-
Allocation on stack(局部变量)。
避免在栈上申请大数组
,其初始化和销毁的代价很高。
- Per-cpu data structure(非共享数据结构)。
避免共享量的锁,在thread local里,多核情况下使用局部变量会带来好处
。
-
Reduce call path or call trace(
减少函数调用层次
)。 -
Read&write split(
读写分离
)。 -
Recude duplicated code(
减少冗余代码
)。
#3 工具优化# “工欲善其事,必先利其器”,如果没有工具的支持,性能优化难以实施,谁也不知道哪些地方是严重影响性能的主要矛盾。
使用性能优化的工具,需要考虑以下问题:
- 使用工具是否需要重新执行编译?
- 工具本身对测量结果的影响:
工具对性能的影响必须在一个可接受的范围以内
。
工具能解决的问题:
- 建立性能基线,以作对比;
- 帮助定位性能瓶颈;
- 帮助验证优化方案;
性能测试工具一般由这么几种:
- 收集CPU的性能计数;
- 利用编译器的功能,在函数入口和出口加回调函数;
- 在代码中加入时间测量点;
#4 系统优化# 从系统层面去优化系统往往有更为明显的效果,优化之前,可以思考,是否能够通过扩展系统来达到提高性能的目的:
- Scale up:使用更强的硬件;
- Scale out:使用更多的组件;
如果升级硬件的方法就能解决问题,为什么还要使用修改代码
,调整架构这样大风险的举措呢?(需要考虑成本)
以下是一些常用的系统优化的方法:
- Cache
Cache干什么?保存已经执行过的结果
。
Cache为什么有效?避免已计算过的开销,获取更快的访问。
Cache的难点在哪里?一是快速匹配;二是Cache容量有效,需要较好的替换策略
;
Cache在哪些情况下有效?时间局部性。即当前计算的结果,后续有可能使用到,如果没有时间局部性,反而对架构有害
。
- Lazy Computing
理念就是,不要做多余的事情,最常见的例子就是COW(copy-on-write):http://en.wikipedia.org/wiki/Copy-on-write
COW干什么?写时复制。
COW为什么有效?节省内存复制时间,均匀内存分配时间。
COW的难点在哪里?一是引用计数的使用;二是确认哪些内存是可以共享的。
- read ahead/pre-fetch预读:http://en.wikipedia.org/wiki/Readahead
预读干什么?提前准备所需要的数据。
预读为什么有效?减少等待内存的时间,相当于把多个操作集合成一个。
预读在哪些情况下有效?空间局部性。
- Asynchronous异步:http://en.wikipedia.org/wiki/Asynchronous_I/O
异步干什么?异步是一种通信方式,请求与应答分离。
异步为什么有效?消除等待时间。
异步的难点是什么?如何实现分布式状态机,如何使用回调,使异步时间到达后继续执行。
异步在哪些情况下有效?状态之间不能有强依赖关系。
-
Polling轮询:http://en.wikipedia.org/wiki/Polling_(computer_science)
-
Static memory pool(内存池):http://en.wikipedia.org/wiki/Static_memory_allocation
内存池干什么?提前分配内存以获取更好的性能,只是适应性可能会降低,并可能造成内存浪费。
内存池为什么有效?避免重复内存申请、释放开销。
内存池的难点是什么?分配多大的内存池,如何避免浪费都是需要考虑的问题。
内存池在哪些情况下有效?一是固定大小的内存需求,二是快速的分配与释放需求。
#5 总结# 性能优化只是系统的一个方面,它可能会和系统的其他要求有冲突,比如:
可读性
:性能优化不能影响可读性,谁愿意维护不怎么漂亮的代码;模块化
:性能优化往往需要打破模块的边界,想想这是否值得;可移植
:隔离硬件相关的代码,尽量使用统一的API;可维护
:许多性能优化的技巧,会导致后来维护代码的人崩溃;
需要在性能优化和上述的几个要求之间做出tradeoff,不能一意孤行。