1.3 效率
程序员似乎被效率问题困扰着。他们可能花费数小时来微调代码,使之运行得更快。遗憾的是,大部分这种工作都是无用功。当猜测程序的运行时间花费在何处时,程序员的直觉非常糟糕。
微调程序是为了使之更快,但通常总是会使之更大、更难理解、更可能包含错误。除非对执行时间的测量表明程序太慢,否则这样的微调没有意义。程序只需要足够快即可,不一定要尽可能快。
微调通常在“真空”中完成。如果一个程序太慢,找到其瓶颈的唯一途径就是测量它。程序的瓶颈很少出现在预期位置或者是因你所怀疑的原因导致,而且在错误位置上微调程序是没有意义的。在找到正确的位置后,仅当该处花费的时间确实占运行时间的很大比例时,才有必要进行微调。如果I/O占了程序运行时间的60%,在搜索例程中节省1%是无意义的。
微调通常会引入错误。最快崩溃的程序绝非胜者。可靠性比效率更重要;与交付足够快的可靠软件相比,交付快速但会崩溃的软件,从长远看来代价更高。
微调经常在错误的层次上进行。快速算法的直接简明的实现,比慢速算法的手工微调实现要好得多。例如,减少线性查找的内层循环的指令数,注定不如直接使用二分查找。
微调无法修复低劣的设计。如果程序到处都慢,这种低效很可能是设计导致的。当基于编写得很糟糕或不精确的问题说明给出设计时,或者根本就没有总体设计时,就会发生这种令人遗憾的情况。
本书中大部分代码都使用了高效的算法,具有良好的平均情况性能,其最坏情形性能也易于概括。对大多数应用程序来说,这些代码对典型输入的执行时间总是足够快速的。当某些程序的代码性能可能会导致问题时,书中自会明确注明。
一些C程序员在寻求提高效率的途径时,大量使用宏和条件编译。只要有可能,本书将避免使用这两种方法。使用宏来避免函数调用基本上是不必要的。仅当客观的测量结果表明有问题的调用的开销大大超出其余代码的运行时间时,使用宏才有意义。操作I/O是较适宜采用宏的少数情况之一。例如,标准的I/O函数getc、putc、getchar和putchar通常实现为宏。
条件编译通常用于配置特定平台或环境的代码,或者用于代码调试的启用/禁用。这些问题是实际存在的,但条件编译通常只是解决问题的较为容易的方法,而且总会使代码更难于阅读。而重写代码以便在执行期间选择平台依赖关系通常则更为有用。例如,一个编译器可以在执行时选择多种(比如说6种)体系结构中的一个来生成代码,这样的一种交叉编译器要比必须配置并搭建6个不同的编译器更有用,而且可能更易于维护。
如果应用程序必须在编译时配置,与C语言的条件编译工具相比,版本控制工具更擅长完成该工作。这样,代码中就不必充斥着预处理器指令,因为那会使代码难于阅读,并模糊被编译和未被编译的代码之间的界限。使用版本控制工具,你看到的代码即为被执行的代码。对于跟踪性能改进情况来说,这些工具也是理想的选择。