《C语言接口与实现:创建可重用软件的技术》一1.3 效率

1.3 效率

程序员似乎被效率问题困扰着。他们可能花费数小时来微调代码,使之运行得更快。遗憾的是,大部分这种工作都是无用功。当猜测程序的运行时间花费在何处时,程序员的直觉非常糟糕。

微调程序是为了使之更快,但通常总是会使之更大、更难理解、更可能包含错误。除非对执行时间的测量表明程序太慢,否则这样的微调没有意义。程序只需要足够快即可,不一定要尽可能快。

微调通常在“真空”中完成。如果一个程序太慢,找到其瓶颈的唯一途径就是测量它。程序的瓶颈很少出现在预期位置或者是因你所怀疑的原因导致,而且在错误位置上微调程序是没有意义的。在找到正确的位置后,仅当该处花费的时间确实占运行时间的很大比例时,才有必要进行微调。如果I/O占了程序运行时间的60%,在搜索例程中节省1%是无意义的。

微调通常会引入错误。最快崩溃的程序绝非胜者。可靠性比效率更重要;与交付足够快的可靠软件相比,交付快速但会崩溃的软件,从长远看来代价更高。

微调经常在错误的层次上进行。快速算法的直接简明的实现,比慢速算法的手工微调实现要好得多。例如,减少线性查找的内层循环的指令数,注定不如直接使用二分查找。

微调无法修复低劣的设计。如果程序到处都慢,这种低效很可能是设计导致的。当基于编写得很糟糕或不精确的问题说明给出设计时,或者根本就没有总体设计时,就会发生这种令人遗憾的情况。

本书中大部分代码都使用了高效的算法,具有良好的平均情况性能,其最坏情形性能也易于概括。对大多数应用程序来说,这些代码对典型输入的执行时间总是足够快速的。当某些程序的代码性能可能会导致问题时,书中自会明确注明。

一些C程序员在寻求提高效率的途径时,大量使用宏和条件编译。只要有可能,本书将避免使用这两种方法。使用宏来避免函数调用基本上是不必要的。仅当客观的测量结果表明有问题的调用的开销大大超出其余代码的运行时间时,使用宏才有意义。操作I/O是较适宜采用宏的少数情况之一。例如,标准的I/O函数getc、putc、getchar和putchar通常实现为宏。

条件编译通常用于配置特定平台或环境的代码,或者用于代码调试的启用/禁用。这些问题是实际存在的,但条件编译通常只是解决问题的较为容易的方法,而且总会使代码更难于阅读。而重写代码以便在执行期间选择平台依赖关系通常则更为有用。例如,一个编译器可以在执行时选择多种(比如说6种)体系结构中的一个来生成代码,这样的一种交叉编译器要比必须配置并搭建6个不同的编译器更有用,而且可能更易于维护。

如果应用程序必须在编译时配置,与C语言的条件编译工具相比,版本控制工具更擅长完成该工作。这样,代码中就不必充斥着预处理器指令,因为那会使代码难于阅读,并模糊被编译和未被编译的代码之间的界限。使用版本控制工具,你看到的代码即为被执行的代码。对于跟踪性能改进情况来说,这些工具也是理想的选择。

时间: 2024-11-02 15:45:26

《C语言接口与实现:创建可重用软件的技术》一1.3 效率的相关文章

《C语言接口与实现:创建可重用软件的技术》一导读

前言 C语言接口与实现:创建可重用软件的技术 如今的程序员忙于应付大量关于API(Application Programming Interface)的信息.但是,大多数程序员都会在其所写的几乎每一个应用程序中使用API并实现API的库,只有少数程序员会创建或发布新的能广泛应用的API.事实上,程序员似乎更喜欢使用自己搞的东西,而不愿意查找能满足他们要求的程序库,这或许是因为写特定应用程序的代码要比设计可广泛使用的API容易. 不好意思,我也未能免俗:lcc(我和Chris Fraser为ANS

《C语言接口与实现:创建可重用软件的技术》一第1章 引言1.1 文学程序

第1章 引言 C语言接口与实现:创建可重用软件的技术 一个大程序由许多小的模块组成.这些模块提供了程序中使用的函数.过程和数据结构.理想情况下,这些模块中大部分都是现成的并且来自于库,只有那些特定于现有应用程序的模块需要从头开始编写.假定库代码已经全面测试过,而只有应用程序相关的代码会包含bug,那么调试就可以仅限于这部分代码. 遗憾的是,这种理论上的理想情况实际上很少出现.大多数程序都是从头开始编写,它们只对最低层次的功能使用库,如I/O和内存管理.即使对于此类底层组件,程序员也经常编写特定于

《C语言接口与实现:创建可重用软件的技术》一2.4 客户程序的职责

2.4 客户程序的职责 接口是其实现和其客户程序之间的一份契约.实现必须提供接口中规定的功能,而客户程序必须根据接口中描述的隐式和显式的规则来使用这些功能.程序设计语言提供了一些隐式规则,来支配接口中声明的类型.函数和变量的使用.例如,C语言的类型检查规则可以捕获接口函数的参数的类型和数目方面的错误. C语言的用法没有规定的或编译器无法检查的规则,必须在接口中详细说明.客户程序必须遵循这些规则,实现必须执行这些规则.接口通常会规定未检查的运行时错误(unchecked runtime error

《C语言接口与实现:创建可重用软件的技术》一2.6 扩展阅读

2.6 扩展阅读 自20世纪50年代以来,过程和函数库的重要性已经是公认的.[Parnas 1972]一文是一篇典型的论文,讨论了如何将程序划分为模块.该论文的历史已经将近40年,但当今的程序员仍然面临着该文所考虑的问题. C程序员每天都使用接口:C库是15个接口的集合.标准输入输出接口,即stdio.h,定义了一个ADT FILE,以及对FILE指针的操作.[Plauger,1992]一书详细描述了这15个接口及适当的实现,其叙述方式大体上类似于本书讨论一组接口和实现的方式. Modula-3

《C语言接口与实现:创建可重用软件的技术》一2.2 实现

2.2 实现 实现会导出接口.它定义了必要的变量和函数,以提供接口规定的功能.实现具体解释了接口的语义,并给出其表示细节和算法,但在理想情况下,客户程序从来都不需要看到这些细节.不同的客户程序可以共享实现的目标码,通常是从(动态)库加载实现的目标码. 一个接口可以有多个实现.只要实现遵循接口的规定,完全可以在不影响客户程序的情况下改变实现.例如,不同的实现可能会提供更好的性能.设计完善的接口会避免对特定机器的依赖,但也可能强制实现依赖于机器,因此对用到接口的每种机器,可能都需要一个不同的实现(也

《C语言接口与实现:创建可重用软件的技术》一2.3 抽象数据类型

2.3 抽象数据类型 一个抽象数据类型是一个接口,它定义了一个数据类型和对该类型的值所进行的操作.一个数据类型是一个值的集合.在C语言中,内建的数据类型包括字符.整数.浮点数等.而结构本身也能定义新的类型,因而可用于建立更高级类型,如列表.树.查找表等. 高级类型是抽象的,因为其接口隐藏了相关的表示细节,并只规定了对该类型值的合法操作.理想情况下,这些操作不会暴露类型的表示细节,因为那样可能使客户程序隐含地依赖于具体的表示.抽象数据类型或ADT的标准范例是栈.其接口定义了栈类型及其5个操作: 〈

《C语言接口与实现:创建可重用软件的技术》一2.5 效率

2.5 效率 本书中的接口的大多数实现所使用的算法和数据结构,其平均情况运行时间不会超过N(输入规模)的线性函数,大多数算法都能够处理大量的输入.无法处理大量输入的接口,或者性能可能成为重要影响因素的接口,可以规定性能标准(performance criteria).实现必须满足这些标准,客户程序可以预期性能能够达到标准的规定(但不会比标准好上多少). 本书中所有的接口都使用了简单但高效的算法.在N较大时,更复杂的算法和数据结构可能有更好的性能,但N通常比较小.大多数实现都只使用基本的数据结构,

《C语言接口与实现:创建可重用软件的技术》一2.7 习题

2.7 习题 2.1 原本可使用预处理器宏和条件编译指令如#if,来指定Arith_div和Arith_mod中如何处理除法的舍入操作.解释为什么对-13/5 == -2的显式测试是实现上述判断的更好的方法. 2.2 对于Arith_div和Arith_mod来说,仅当用于编译arith.c的编译器执行算术操作的方式与Arith_div和Arith_mod被调用时的目标机器相同时,这两个函数中所用的-13/5 == -2测试才是有效的.但这个条件可能会不成立,例如,如果arith.c由运行在机器

《C语言接口与实现:创建可重用软件的技术》一1.2 程序设计风格

1.2 程序设计风格 double说明了本书中程序所使用的风格惯例.程序能否更容易被阅读并理解,比使程序更容易被计算机编译更为重要.编译器并不在意变量的名称.代码的布局或程序的模块划分方式.但这种细节对程序员阅读以及理解程序的难易程度有很大影响. 本书代码遵循C程序的一些既定的风格惯例.它使用一致的惯例来命名变量.类型和例程,并在本书的排版约定下,采用一致的缩进风格.风格惯例并非是一种必须遵循的刚性规则,它们表示的是程序设计的一种哲学方法,力求最大限度地增加程序的可读性和可理解性.因而,凡是改变