为什么软件运行缓慢?

如果世界上所有软件都能高效利用所有可用的硬件资源,那么这本书就没有存在的必要了。我们不需要在软件层面做任何改变,只需依赖现有处理器所能提供的一切就够了。但你已经知道,现实并非如此,对吧?现实是,现代软件极其低效。公共云中的普通服务器系统通常运行着优化不足的代码,消耗着本可以节省的大量电力(增加了碳排放,并造成其他环境问题)。如果我们能让所有软件速度提升两倍,就有可能将计算的碳足迹减少一半。

论文 [Leisersoneaam9744] 的作者提供了一个绝佳的示例,说明了"默认"软件与高度优化软件之间的性能差距。表 PlentyOfRoom 总结了对一个将两个 4096×4096 矩阵相乘的程序进行性能工程后所获得的加速比。应用多项优化后的最终结果是,程序速度提升了超过 60,000 倍。提供这个示例的目的不是要批评 Python 或 Java(它们都是优秀的编程语言),而是要打破"软件默认就有足够好的性能"这一错误认知。大多数程序都处于第 1 至第 5 行的水平,源代码层面的改进潜力相当可观。

版本 实现方式 绝对加速比 相对加速比
1 Python 1
2 Java 11 10.8
3 C 47 4.4
4 并行循环 366 7.8
5 并行分治 6,727 18.4
6 加向量化 23,224 3.5
7 加 AVX 内联函数 62,806 2.7

表:对在配备总共 60 GB 内存的双路 Intel Xeon E5-2666 v3 系统上运行的两个 4096×4096 矩阵相乘程序进行性能工程所获得的加速比。摘自 [Leisersoneaam9744]。

那么,究竟是什么阻止了系统默认达到最优性能?以下是几个最重要的因素:

  1. CPU 的局限性:人们很容易会问:"为什么硬件不能解决我们所有的问题?" 现代 CPU 执行指令的速度非常快,并且每一代都在进步。但如果执行任务所用的指令不够优化甚至是多余的,CPU 也无能为力。处理器无法神奇地将次优代码转化为性能更好的代码。例如,如果我们实现了一个冒泡排序,CPU 不会尝试识别它并使用更好的替代方案(如快速排序),它只会盲目执行被要求执行的操作。
  2. 编译器的局限性:"但这不正是编译器该做的事吗?为什么编译器不能解决我们所有的问题?" 确实,现代编译器非常智能,但仍然可能生成次优代码。编译器擅长消除冗余工作,但在做出更复杂的决策(如向量化(vectorization))时,可能无法生成最优代码。性能专家往往能想出比编译器能力更强的循环向量化方法。当编译器需要决定是否进行某项代码转换时,它依赖复杂的代价模型(cost models)和启发式算法(heuristics),这些方法并不适用于所有可能的场景。例如,对于"编译器是否应该始终将一个函数内联(inline)到调用处"这个问题,没有简单的"是"或"否"的答案,它通常取决于编译器需要考量的许多因素。此外,编译器只有在绝对确定安全的情况下才会执行优化,要证明某项优化在所有可能情况下都是正确的非常困难,这会阻止某些代码转换。最后,编译器通常不会尝试"大手术"式的优化,比如改变程序使用的数据结构。
  3. 算法复杂度分析的局限性:一些开发者对算法复杂度分析过于执迷,这导致他们选择具有最优算法复杂度的流行算法,即使该算法对于给定问题可能并非最高效的。以插入排序和快速排序这两种排序算法为例,从平均情况的大 O 表示法来看,快速排序明显优于插入排序:插入排序是 O(N^2^),而快速排序只有 O(N log N)。然而,当 N 的值相对较小(不超过 50 个元素)时,插入排序反而优于快速排序。复杂度分析无法涵盖各种算法的所有底层性能影响,人们只是将它们隐含在一个常数 C 中,而这个常数有时会对性能产生重大影响。仅仅计算排序中使用的比较和交换次数,忽略了缓存未命中(cache misses)和分支预测错误(branch mispredictions),而这些在今天实际上代价非常高昂。盲目信任大 O 表示法而不在目标工作负载上进行测试,可能会引导开发者走上错误的道路。因此,对于某个特定问题,最广为人知的算法并不一定是在实践中对所有可能输入都表现最好的。

除了上述局限性之外,编程范式(programming paradigms)也带来了额外开销。优先考虑代码清晰度、可读性和可维护性的编码实践可能会降低性能。高度通用化和可复用的代码可能引入不必要的拷贝、运行时检查、函数调用、内存分配等。例如,面向对象编程(object-oriented programming)中的多态性(polymorphism)通常通过虚函数(virtual functions)实现,这会引入性能开销。1

上述所有因素都会对软件征收"性能税"。优化软件性能以充分发挥其潜力,往往存在相当大的空间。

1. 我并不否认设计模式和整洁代码原则的价值,但我鼓励采用更加细致的方法,将性能也作为开发过程中的一个关键考量因素。

results matching ""

    No results matching ""