
1.1 软件优化概述
软件优化是一个涉及面非常广泛的主题,优化的对象可以是性能、功耗、存储容量等不同方面。本书主要讨论提升系统性能的软件优化技术。
1.1.1 软件优化的主要方法
软件优化可以在编译器(解释器)、库、操作系统等软件层面进行,也可以从CPU的指令系统和体系结构,或者磁盘/网络等硬件角度考虑,如图1-1所示。很多时候,需要综合使用多个层次的优化技术。
在各种优化方式中,编译器层面的优化较为简单。主要是设置编译选项,提升编译器自主优化的级别。在某些编译器中,可以在源代码中加入编译制导语句,指导编译器产生更优化的代码。在某些解释执行的语言(例如Java)中,解释器往往会采用JIT(JustIn Time)技术,即自动将解释执行的代码即时编译为高效执行的机器代码,以提升执行效率。

图1-1 软件优化的主要层次
在软件设计中往往会使用库函数,例如高级语言的内存管理库、C++的STL、科学计算的BLAS等。这些库极大地方便了应用软件的开发,往往也是影响应用软件性能的重要因素,需要对经常使用的库进行性能评测,分析其实现原理,掌握它们的主要性能特征和影响库性能的主要因素,以求扬长避短,最大限度发挥这些基础库的技术优势。同样,操作系统调用与库使用有很多类似的地方,也需要深入理解操作系统调用的实现原理和主要性能特点。
应用软件的性能瓶颈可能来自计算能力、存储器访问、文件系统、网络等方面,这些都与底层硬件的技术参数密切相关。在计算能力方面,CPU的核数、主频、流水线结构特征、SIMD(单指令流多数据流)指令支持情况都是影响性能的关键性因素。在存储器访问方面,CPU上的Cache结构和容量参数尤其重要,存储器系统的带宽和延迟也是决定性能的重要因素。文件系统的性能对于大数据处理尤其重要,机械硬盘或者SSD有着不同的性能特点,文件的访问模式对文件系统性能也有着至关重要的影响。在网络方面,不同的网络结构具有不同的延迟和吞吐率,而且多个节点之间的并行软件框架特征也有着很大的不同。需要根据这些底层特征确定软件系统的整体设计和关键运行参数。
一般而言,主要有三种软件性能优化方法。
❑开发并行性。当前微处理器上具备了指令级、数据级、线程级等多个层次的并行性可供软件使用。指令级并行性又分为时间并行性(流水线)、空间并行性(超标量的多指令并行执行)两类,数据级并行性主要是SIMD指令系统的使用,线程级并行性是利用多线程方法并行使用CPU上的多个核。这些内容将分别在本书的第2章、第3章和第4章介绍。
❑延迟隐蔽。延迟隐蔽本质上也是开发系统的并行性,其侧重点是并行运行不同类型的部件,以相互掩盖执行时间。这种方法可以体现在系统的不同层次,例如,通过Cache预取方法掩盖Cache读取内存的延迟;同时执行I/O操作和CPU计算,掩盖I/O操作的延迟。这种方法会贯穿在很多应用场景中,应用非常灵活。
❑操作的规则化。对于硬件系统而言,不规则的操作(例如不规则的存储器访问、不规则的条件分支、零散的I/O访问等)将严重影响硬件效率。需要仔细分析硬件操作的特性,将不规则的硬件操作转化为规则的操作,提升硬件效率。
软件优化往往综合运用多种方法和策略,对同一个应用问题需要综合使用多种手段,有时还需要在不同方法之间进行比较和权衡,以求整个软硬件系统发挥出最大效能。作为一本介绍通用性软件优化方法的书籍,本书主要讨论单个计算节点上的软件优化技术,重点是CPU、存储器系统和文件系统方面的性能优化,以及某些通用库(例如STL)的优化使用。对于特定算法或问题的软件优化,由于涉及较深入的领域知识,不在本书的讨论范围之内。读者可以根据本书提供的主要思路和方法,自行寻找特定问题的优化方法。
1.1.2 软件性能工程
作为软件工程的一个重要组成部分,软件性能工程[2]贯穿在软件需求分析、设计、实现、测试的多个阶段中。需要在软件开发的各个阶段都充分重视性能问题,才能保证最终软件产品能满足最初的性能要求。在软件工程的各个阶段中,性能优化相关的工作如表1-1和图1-2所示。
表1-1 软件工程各个阶段中的软件性能优化

在软件的需求分析阶段,主要考虑性能指标和可移植两个方面的问题。性能指标既要符合应用的需要,又要在运行平台能提供的性能范围内。必要时需要对关键性能参数进行预先测试和估计,以找到合理的性能指标。在设定软件性能指标时,需要预先明确以下测试要素。
❑工作负载,包括作业类型、负载随时间变化的特征、输入数据量、输出数据量等。
❑软件平台,包括操作系统、编译器、网络协议、所使用的服务和基础库等。
❑硬件平台,包括CPU型号、内存的容量和带宽、网络系统与硬盘的容量和带宽等。
❑性能指标描述,可以是响应时间也可以是吞吐率,还有可能包括功耗、存储容量等。
❑性能测试,确定使用标准的测试程序(Benchmark),还是使用工作环境中的实际软件和数据。
❑软件生命周期内可能需要迁移的新运行平台,例如不同种类的CPU或者操作系统,尽可能列举未来所使用平台的主要参数。

图1-2 软件性能工程的总体流程
在软件设计阶段,首先需要为整个软件系统建立模型,分析软件的运行流程,预判软件中影响性能的关键模块,推算这些关键模块所应达到的性能指标要求。其次需要列举这些关键模块的主要设计方案,进行理论评估,必要时需要事先设计并实现关键模块,以评估其性能是否满足要求。在软件结构设计方面,主要有并行化软件结构设计和可移植性两个方面需要考虑。在并行化软件结构设计方面,需要考虑采用何种并行化模型实现软件的并行化总体结构。在可移植性方面,需要设计能够适应不同平台特征的公共接口,为后续软件的移植提供灵活的框架。
在软件实现和测试阶段,应该首先快速实现一个简单而正确的版本,作为后续性能调优的基准和正确性检查的测试案例来源。在此基础上,使用多种软件优化方法对关键模块进行优化和性能测试。在关键模块的性能达到预期指标后,再按照需求分析阶段确定的性能测试方法测试软件的性能。如果没有达到性能指标要求,就需要对整个软件重新进行热点分析,寻找影响性能的瓶颈。如果发现性能瓶颈不是设计阶段估计的关键模块,就需要对新的关键模块进行更为详细的测试,再综合各种软件优化方法对其进行优化,直至达到性能要求。
1.1.3 关于软件优化的一些观点
本节将评述关于软件优化的常见观点。
观点一:过早的优化是万恶之源。
这是一种非常流行的说法。按照这样的说法,软件优化往往是在构成正确运行的软件之后才进行的。我们认同应该尽快构建能正确运行的软件系统,但并不是一开始就构造充分优化的系统。但是,如果在设计之初没有考虑到软件优化的关键性问题,例如线程结构、可以并行的核心数据结构和算法、可移植性等,而是在发现性能瓶颈时才去重新调整和优化已有的软件结构,那么将使得整个软件被迫进行大规模修改和测试,从而严重地影响软件的开发进度和质量。
我们认为软件的性能是软件需求分析中不可或缺的一个部分,需要在初始设计时就对性能指标做出严格的规定,同时要分析决定系统性能的主要因素,并在设计软件结构时就充分考虑如何实现关键性能指标,选择合理的优化方法(虽然不一定在开始时就实现),在线程结构、数据结构和算法选择上为后续的软件优化方法预留空间,尽可能减少性能优化过程对软件整体结构的修改。
观点二:性能优化一定需要非常底层的知识和技术,例如汇编语言编程。
毫无疑问,对计算机体系结构、汇编语言、操作系统、编译器和库等基础知识非常了解,将大大有助于性能优化的过程。但是,软件优化有很多层次,特别是现代编译技术的发展,使得很多性能优化工作在高级语言层面就可以实现。根据我们的经验,性能优化的努力和效果曲线大致如图1-3所示。

图1-3 性能优化的努力与效果
利用现代编译技术、优化的软件库等对源代码进行少量修改,就可以使得性能有较为明显的提升。但是要让性能逼近系统所能达到的性能极限,则需要花费很大的气力,比如使用包括汇编语言编程在内的多种优化方法。
观点三:软件优化主要依靠算法。
算法的优化与改进往往可以在算法复杂度层面提升软件系统的性能,这对于大规模的计算尤其有帮助。但是必须注意算法是否具有体系结构的友好性,即算法能否在现代微处理器等硬件系统上高效运行。以排序算法为例,虽然快速排序算法的复杂度为O(NlogN),但是由于其访存不规范、难以并行化,在现代微处理器上基数排序等对体系结构友好的排序方法往往实际性能更好。
需要注意的是,现代微处理器体系结构的快速发展(例如多核处理器、GPU、SIMD指令系统等)使得很多传统算法需要重新设计和修改,以适应不同体系结构的特征。针对不同体系结构特点的优化也一直是当前算法研究的一个重要领域。
观点四:软件优化往往针对特定体系结构,可移植性比较差。
软件优化技术往往与特定的体系结构密不可分,这的确会影响软件优化的可移植性。可以通过优化软件结构的方法提升软件系统的可移植性。例如,在不同硬件平台上使用针对不同体系结构的库,但是库的接口保持一致,从而保持高层软件的一致性,减少跨平台移植的难度和工作量。因此,需要在软件总体设计阶段就预先为不同平台确定一个统一的接口层,以方便后续的跨平台移植。
观点五:软件优化永无止境。
并非需要永无止境地进行软件优化,其终止条件一般有两个:①已经达到了预期的指标要求;②已经非常接近系统的性能极限。可以根据应用的实际需求来确定第一个条件,但是要确定系统的性能极限往往比较困难,需要详细分析整个系统的性能瓶颈,然后进行推算和估计才能得到初步的结论。
观点六:软件优化需要对整个软件系统进行。
我们往往只对软件的“热点”(即最影响性能的部分)进行优化,以期以最小的成本获得最大的收益。软件优化的一般过程往往是:发现热点,分析热点形成的原因,再对热点进行优化。值得注意的是,当完成一个热点的修改后,原先不是热点的软件模块有可能成为新的热点。因此需要反复使用上述热点优化方法进行改进,从而最终达到性能要求。