预计阅读本页时间:-
13.4.3 空间-时间的权衡
改进性能的一种一般性的方法是权衡时间与空间。在一个使用很少内存但是速度比较慢的算法与一个使用很多内存但是速度更快的算法之间进行选择,这在计算机科学中是经常发生的事情。在做出重要的优化时,值得寻找通过使用更多内存加快了速度的算法,或者反过来通过做更多的计算节省了宝贵的内存的算法。
一种常用而有益的技术是用宏来代替小的过程。使用宏消除了通常与过程调用相关联的开销。如果调用出现在一个循环的内部,这种获益尤其显著。例如,假设我们使用位图来跟踪资源,并且经常需要了解在位图的某一部分中有多少个单元是空闲的。为此,我们需要一个过程bit_count来计数一个字节中值为1的位的个数。图13-7a中给出了简单明了的过程。它对一个字节中的各个位循环,每次计数它们一次。

该过程有两个低效的根源。首先,它必须被调用,必须为它分配栈空间,并且必须返回。每个过程调用都有这个开销。第二,它包含一个循环,并且总是存在与循环相关联的某些开销。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
一种完全不同的方法是使用图13-7b中的宏。这个宏是一个内联表达式,它通过对参数连续地移位,屏蔽除低位以外的其他位,并且将8个项相加,这样来计算位的和。这个宏决不是一件艺术作品,但是它只在代码中出现一次。当这个宏被调用时,例如通过
sum=bit_count(table[i]);
这个宏调用看起来与过程调用等同。因此,除了定义有一点凌乱以外,宏中的代码看上去并不比过程中的代码要差,但是它的效率更高,因为它消除了过程调用的开销和循环的开销。
我们可以更进一步研究这个例子。究竟为什么计算位计数?为什么不在一个表中查找?毕竟只有256个不同的字节,每个字节具有0到8之间的惟一的值。我们可以声明一个256项的表bits,每一项(在编译时)初始化成对应于该字节值的位计数。采用这一方法在运行时根本就不需要计算,只要一个变址操作就可以了。图13-7c中给出了做这一工作的宏。
这是用内存换取计算时间的明显的例子。然而,我们还可以再进一步。如果需要整个32位字的位计数,使用我们的bit_count宏,每个字我们需要执行四次查找。如果将表扩展到65 536项,每个字查找两次就足够了,代价是更大的表。
在表中查找答案可以用在其他方面。例如,在第7章中,我们看到了JPEG图像压缩是怎样工作的,它使用了相当复杂的离散余弦变换。另一种压缩技术GIF使用表查找来编码24位RGB图像。然而,GIF只对具有256种颜色或更少颜色的图像起作用。对于每幅要压缩的图像,构造一个256项的调色板,每一项包含一个24位的RGB值。压缩过的图像于是包含每个像素的8位索引,而不是24位颜色值,增益因子为3。图13-8中针对一幅图像的一个4×4区域说明了这一思想。原始未压缩的图像如图13-8a所示,该图中每个取值是一个24位的值,每8位给出红、绿和蓝的强度。GIF图像如图13-8b所示,该图中每个取值是一个进入调色板的8位索引。调色板作为图像文件的一部分存放,如图13-8c所示。实际上,GIF算法的内容比这要多,但是思想的核心是表查找。

存在减少图像大小的另一种方法,并且这种方法说明了一种不同的权衡。PostScript是一种程序设计语言,可以用来描述图像。(实际上,任何程序设计语言都可以描述图像,但是PostScript专为这一目的进行了调节。)许多打印机具有内嵌的PostScript解释器,能够运行发送给它们的PostScript程序。
例如,如果在一幅图像中存在一个像素矩形块具有相同的颜色,用于该图像的PostScript程序将携带指令,用来将一个矩形放置在一定的位置并且用一定的颜色填充该矩形。只需要少数几个位就可以发出此命令。当打印机接收图像时,打印机中的解释器必须运行程序才能绘制出图像。因此,PostScript以更多的计算为代价实现了数据压缩,这是与表查找不同的一种权衡,但是当内存或带宽不足时是颇有价值的。
其他的权衡经常牵涉数据结构。双向链表比单向链表占据更多的内存,但是经常使得访问表项速度更快。散列表甚至更浪费空间,但是要更快。简而言之,当优化一段代码时要考虑的重要事情之一是:使用不同的数据结构是否将产生最佳的时间-空间平衡。