性能优化的方法和技巧:系统
系列目录 性能优化方法和技巧
- 性能优化的方法和技巧:概述
- 性能优化的方法和技巧:代码
- 性能优化的方法和技巧:工具
- 性能优化的方法和技巧:系统
从系统层次去优化系统往往有比较明显的效果。但是,在优化之前,我们先要问一问,能否通过扩展系统来达到提高性能的目的,比如:
- Scale up: 用更强的硬件替代当前的硬件
- Scale out: 用更多的部件来增强系统的性能
使用更强的硬件当然和优化没有半点关系,但是如果这是一个可以接受的方案,为什么不用这个简单易行的方案哪?替换硬件的风险要比改架构,改代码的风险小多了,何乐而不为?
Scale out的方案就有一点麻烦。它要求系统本身是支持scale out,或者把系统优化成可以支持scale out。不管是哪一种选择,都不是一个简单的选择。设计一个可以scale out的系统已经超出了本文所要关注的范围,但是,scale out应该是系统优化的一个重要方向。
下面会讨论一些常见的系统优化的方法,如果还有其他没有提到的,也欢迎读者指出来。
1) Cache
- Cache是什么?Cache保存了已经执行过的结果。
- Cache为什么有效?一是可以避免计算的开销(比如SQL查询的开销);二是离计算单元更近,所以访 问更快(比如CPU cache)。
- Cache的难点在哪里?一是快速匹配,这涉及到匹配算法选择(一般用哈希表),Cache容量(哈希表的容量影响查找速度);二是替换策略(一般使用LRU或者随机替换等等)。
- Cache在哪些情况下有效?毫无疑问,时间局部性,也就是当前的结果后面会用到,如果没有时间局部性,Cache就不能提高性能,反而对性能和系统架构有害处。所以在系统设计之初,最好是审视一下数据流程,再决定是否引入Cache层。
2) Lazy computing
Lazy computing(延迟计算),简而言之,就是不要做额外的事情,特别是无用的事情。最常见的一个例子就是COW(copy on write),可以参考这个链接http://en.wikipedia.org/wiki/Copy-on-write。
- COW是什么?写时复制。也就是说fork进程时,子进程和父进程共享相同的代码段和数据段,如果没有写的动作发生,就不要为子进程分配新的数据段(通常在fork之后,会有exec,用新的代码段和数据段替换原来的代码段和数据段,所以复制父进程的数据段是没有用的)。
- COW为什么有效?一是可以节省复制内存的时间,二是可以节省内存分配的时间(到真正需要时再分配,虽然时间不会减少,但是CPU的使用更加均匀,避免抖动)。
- COW的难点在哪里?一是引用计数,多个指针指向同一块内存,如果没有引用计数,内存无法释放;二是 如何知道哪块内存是可以共享的?(在fork的例子里面,父进程,子进程的关系非常明确,但是在有些应用里面,需要查找能够共享的内存,查找需要花时间)
Lazy computing在哪些情况下有效?目前能想到的只有内存复制。用时分配内存算不算哪?用时分配内存不能节省时间,但是可以节省空间。静态内存对时间性能有好处;动态内存对空间性能有好处。就看目标是优化哪个性能了。
3) Read ahead
Read ahead (预读),也可以称之为pre-fetch(预取)。就是要提前准备所需要的数据,避免使用时的等待。
- Read ahead是什么?可以参考http://en.wikipedia.org/wiki/Readahead,这个是讲文件预读的。CPU里面也有pre-fetch(CPU预取需要仔细安排,最好是能够填充流水线,所以需要多次尝试才有结果)。
- Read ahead为什么有效?Read ahead可以减少等待内存的时间。其实相当于把多个读的动作集合成一个。这个和网络里面的buffering或者sliding window有异曲同工之妙。停-等协议是最简单的,但是效率也最低。
- Read ahead的难点在哪里?预读多少才合适?预读窗口的大小需要根据负载,文件使用的多少等因素动态调整。预测的成功与否关系的性能。所以这并不是一个简单的优化方法。
- Read ahead在哪些情况下有效?毫无疑问,空间局部性。没有空间局部性,read ahead就失去了用武之地。用错了,反而会降低性能。
4) Hardware assist
Hardware assist (硬件辅助),顾名思义,就是用硬件实现某些功能。常见的,比如加密,解密;正则表达式或者DFA engine,或者规则查找,分类,压缩,解压缩等等。逻辑简单,功能确定,CPU intensive的工作可以考虑用硬件来代替。
- Hardware assist为什么有效?协处理器可以减轻CPU的工作,而且速度比CPU做要快(这个要看情况,并不是任何情况下都成立)。Hardware assist和Hardware centric的设计完全不同,不能混为一谈。在Hardware assist的设计里面,主要工作还是由软件完成;而hardware centric就是基于ASIC的设计方案,大部分工作是有硬件来完成。
- Hardware assist的难点在哪里?一是采用同步还是异步的方式与硬件交互(通常是异步);二是如何使硬件满负荷工作,同时又避免缓冲区溢出或丢弃(这个要安排好硬件和软件的节奏,使之协调工作);还有就是硬件 访问内存的开销(尽量硬件本身所带的内存,如果有的话)。
5) Asynchronous
Asynchronous(异步)。同步,异步涉及到消息传递。一般来说,同步比较简单,性能稍低;而异步比较复杂,但是性能较高。
- Asynchronous是什么?异步的含义就是请求和应答分离,请求和应答可以由不同的进程或线程完成。比如在 TCP协议的实现里面,如果滑动窗口是1,那么每次只能发送一个字节,然后等待应答;如果增加滑动窗口,那么一次可以发送多个字节,而无需等待前一个字节的应答。这样可以提高性能。
- Asyncrhonous为什么有效?异步消除了等待的时间,可以更有效利用带宽。
- Asynchronous的难点是什么?一是如何实现分布式的状态机?由于请求和应答双方是独立的,所以要避免状态之间有依赖关系,在无法消除状态之间的依赖关系时,必须使用同步消息(比如三次握手);二是应答来了之后, 如果激活原来的执行过程,使之能够继续执行。
- Asynchronous在哪些情况下有效?很明显,状态之间不能有依赖关系,同时需要足够的带宽(或者窗口)。
6) Polling
Polling(轮询)。Polling是网络设备里面常用的一个技术,比如Linux的NAPI或者epoll。与之对应的是中断,或者是事件。
- Polling为什么有效?Polling避免了状态切换的开销,所以有更高的性能。
- Polling的难点是什么?如果系统里面有多种任务,如何在polling的时候,保证其他任务的执行时间?Polling 通常意味着独占,此时系统无法响应其他事件,可能会造成严重后果。
- Polling在哪些情况下有效?凡是能用事件或中断的地方都能用polling替代,是否合理,需要结合系统的数据流程来决定。
7) Static memory pool
Static memory pool(静态内存)。如前所述,静态内存有更好的性能,但是适应性较差(特别是系统里面有多个 任务的时候),而且会有浪费(提前分配,还没用到就分配了)。
- Static memory pool为什么有效?它可以使内存管理更加简单,避免分配和是否内存的开销,并且有利于调试内存问题。
- Static memory pool的难点在哪里?分配多大的内存?如何避免浪费?如何实现O(1)的分配和释放?如何初始化内存?
- Static memory pool在哪些情况下有效?一是固定大小的内存需求(通常与系统的capacity有关),内存对象的大小一致,并且要求快速的分配和释放。
系统层次的优化应该还有很多方法,能想起来的就这么多了(这部分比较难,酝酿了很久,才想起来这么一点东西^-^),读者如果有更好的方法,可以一起讨论。性能优化是关注实践的工作,任何纸上谈兵都是瞎扯,与读者共勉。
参考资料:
1:http://en.wikipedia.org/wiki/Copy_on_write
2:http://en.wikipedia.org/wiki/Readahead
3:http://en.wikipedia.org/wiki/Sliding_window
4:http://en.wikipedia.org/wiki/Asynchronous_I/O
5:http://en.wikipedia.org/wiki/Coprocessor
6:http://en.wikipedia.org/wiki/Polling_(computer_science)
7:http://en.wikipedia.org/wiki/Static_memory_allocation
8:http://en.wikipedia.org/wiki/Program_optimization
9: http://en.wikipedia.org/wiki/Scale_out