第12章 提升性能所需编码准则
12.1 重视性能,限制输出
启动Linux操作系统的计算机时,会看到很多提示信息。虽然初衷是为了输出必要信息,但反复输出相同信息反而会成为一种负担。因此,最好支持用户可以自由设置程序运行时是否输出提示信息。也就是说,用户可以选择输出或不输出这些信息。
用户不满意的地方远不止这些输出信息。C语言中,负责格式化输出的printf()函数的运算成本要远高于其他运算。因为将字符串按照一定格式转换为数字的过程非常复杂,而且将字符输出到标准输出设备的过程同样非常耗时。另外,涉及这一过程的scanf()或sscanf()函数的运算成本也非常高。
printf()函数的运算成本接近加法运算成本的200倍!
12.2 用简单形式改写运算表达式
C编译器处理运算的成本:
位运算、逻辑运算 < 加法运算 < 乘法运算 < 除法运算 < 处理浮点数
所以运算时,应该尽可能选择成本较低的运算。
12.3 需要高效处理大文件时应使用二进制文件
用C语言处理文件时,一般使用ASCII格式的文件。
ASCII格式编写的文件优点是,在任何地方都可以查看该文件,因为任何计算机都提供可以查看ASCII格式文件的命令或程序。而且便于一直,Windows上编写的ASCII文件可以直接用于Linux。
但是,ASCII文件的读写速度要低于二进制文件,而且所占空间更大。因此,如果程序性能至关重要或需要节约存储空间,最好选用二进制文件而非ASCII文件。
12.4 了解并使用压缩/未压缩结构体优缺点
C语言擅长信号处理。实际生活中,随处可见需要信号处理的情况。用网络连接的计算机之间需要进行各种信号往来,根据TCP、IP或ICMP、UDP等规则传送或接收信号。
C语言中,一般将负责这种信号处理的函数编写为结构体形式,使用更加方便。实际上,大部分程序员会单独定义负责信号处理的数据类型,即设置用户自定义数据类型。大部分程序员会用结构体声明这种数据类型。下列示例声明用户自定义数据类型Siganl。
struct Signal {
char input_character;
int error_check;
// …略…
int parity_check;
int carrier_check;
int power_check;
}
假设int型大小为4字节,整个结构体为17字节。假设有需要同时处理1万个信号的极端程序,则声明的数组整体大小为17万个字节,约等于170kB。有的操作系统限制栈大小为64kB。也就是说,栈无法容纳该数组,需要寻找解决方案。
第一个解决方案是将数组声明为静态变量。但这并不是正确的解决方法,因为定义为静态变量可能扰乱整个程序流程。
第二个解决方案是减少Signal大小。如何减少呢?大部分信号只表现on/off。因此,可以用位域定义组成该数据类型的个元素。如下示例所示:
struct Signal {
char input_character;
int error_check:1;
int parity_check:1;
int carrier_check:1;
int power_check:1;
}
像这样,用位域定义负责信号处理的各元素后,Signal总大小减少到2字节之内。因此,即使声明10000个信号处理所需数组,也只需要20000字节,即20kB。这就满足一部分操作系统关于“栈最大为64kB”的限制。
这种使用位域减少所占空间大小的结构体又称压缩结构体。为了节约内存空间,信号处理领域多采用压缩结构体。但压缩结构体会减慢程序运行速度,因为位域运算需要消耗大量时间。
两种结构体各有利弊。程序员应该充分认识这一点,并根据不同情况做出选择。
12.5 根据运行环境选择编程语言
C++、Java、C#运行环境凭心而论,与C++相比,Java和C#更易掌握,也更稳定。而且Java和C#实现了网络环境下的优化。网络中的各计算机的操作系统可能各不相同,即使如此,只要安装JVM或CLR这种虚拟运行环境,即可随时运行Java和C#编写的程序。
正因为Java和C#编写的程序并不依赖计算机操作系统,而需要经过JVM或CLR这一步骤,所以运行速度慢。但C和C++在网络环境下相对有些力不从心。
C++是在C语言种引入面向对象的概念形成的,而Java则在C++中引入混合方式编译器这一构想,C#与Java相似却更简便、更稳定
可能有人会质疑,选择与运行环境相吻合的语言和编码风格有什么关系呢?其实,编码风格的第一步就是选择编程语言。选择何种编程语言对程序的影响程度远大于缩进等细枝末节的影响。
12.6 根据情况选择手段
指针可以大幅提高程序性能,所以市面上的图书常常将擅长使用指针的程序员吹捧为“高级程序员”“有能力的程序员”。编写组成嵌入式系统的程序时,需要程序能够优化系统。而嵌入式系统相对缺少大型内存、CPU的支持,必须考虑效率的问题。这种情况下,能够随心所欲地熟练使用指针地程序员一定会备受推崇。
那么,编写个人PC或企业服务器上运行地程序时,对效率没有极端要求。在这种情况下,能够选择与业务情况、系统情况相吻合的恰当方法处理问题的程序员跟为可贵。例如,在需要最大限度保证可移植性的情况下,即使运行速度减慢、占用内存变多、代码编写更为繁琐,也应该选择数组而非指针,应该选择值传递而非引用传递。
简而言之,擅长使用指针的人不一定时能者。能够从语言提供的各种工具中选择最合适的,并知道如何充分利用该工具才最重要。
12.7 选择更优秀的数据结构
有时,需要处理的数据太多,超出数组可以承受的范围,此时应该选择数组以外的其他数据结构,其中以使用链表为最优。但这并不意味着数组只有缺点没有优点。处理字符串这种较小的数据或数据个数极少时,数组更为方便,即便于处理又便于理解。
因此,应该根据情况选择合适的数据结构。本书之所以介绍这种理论性的内容是因为,众多程序员居然选择利用数组解决所有问题,而不根据情况选择合适的数据类型。有些程序员甚至不知道除数组外还有其他数据结构。
不同程序员可能会用各种方式实现相同的程序设计,此时,数据结构应用能力的高低决定了编程质量的优劣。例如,在没有任何具体排序方式要求的情况下,收到实现排序功能的需求时,程序员可以自由选择排序方式。此时,选用不同的数据结构决定了排序方式和排序性能,所以必须对数据结构由充分认识。最好选择与情况相吻合的数据结构,无论数组、链表、树、队列、栈还是其他数据结构都有各自的优缺点,需要综合考虑并作出最优选择。