核心条款——over-eager evaluation(过度热情计算法) :在要求你做某些事情之前就完成它们。
例如下面模板类,用来表示大量数字类型的集合:
template<class NumericalType>
class DataCollection
{
public:
NumericalType min() const;
NumericalType max() const;
NumericalType avg() const;
};
假如min,max和avg函数分别返回现在这个集合的最小值,最大值和平均值。有三种方法实现这个函数:使用eager evaluation(热情计算法),当min,max和avg函数被调用的时,我们检测集合内所有的数值,然后返回一个合适的值。使用lazy evaluation(懒惰计算法),只有确认需要函数的返回值的时,我们才要求函数能返回能用来确定准确数值的数据结构。使用over-eager evaluation(过度热情计算法),我们随时跟踪目前集合的最小值,最大值和平均值,这样min,max和avg被调用时,我们不用再计算就能立刻返回正确的值。如此频繁调用min,max和avg,我们把跟踪集合最小值,最大值和平均值的开销分摊到所有这些调用函数,每次函数调用索分摊的开销比eager evaluation或lazy evaluation要小。
采用over-eager 最简单的方法是caching(缓存)那些已经计算出来而以后还要可能需要的值。
例如如下代码:你编写一个程序,用来提供有关雇员的信息,这些信息中的经常被需要的部分是雇员的办公隔间号码。假设雇员信息存储在数据库中,但是对于大多数应用程序来说,雇员隔间都是不相关的,所以数据库不对查抄它们进行优化,为了避免你的程序给数据库造成负担,可以编写一个函数findCubicleNumber用来缓存数据。每次查找直接在cache中查找,而不是数据库。
int findCubicleNumber(const string& employeeName)
{
typedef map<string,int> CubicleMap;
static CubicleMap cubes;
CubicleMap::iterator it = cubes.find(employeeName);
if(it == cubes.end())
{
cubes[employeeName] = cubicle; //没找到就插入,再返回当前
return cubicle;
}else
return it->second; //找到直接返回
}
Prefetching(预提取)则是另外一个方法。
例如你又一个dynamic数组数显的模板,dynamic就是开始时具有一定尺寸,以后可以自动扩展的数组。所以所有非负的索引都是合法的:
template<class T>
class DynArray{...}; //dynamic数组的模板
DynArray<double> a; //此时,只有a[0]是合法的数组
a[22] = 3.5; //索引0-22是合法的数组
a[32] = 0; //索引0-32是合法的数组
一个DynArray对象在需要的时候自行扩展?一种最直接的方法按需要分配内存:
template<class T>
T& DynArray<T>::operator[](int index)
{
if(index < 0)
throw an exception;
if(index > 当前最大的索引值)
调用new分配足够的额外内存,以使得索引合法;
返回index位置上的数组元素;
}
每次增加数组长度时,这种方法就要调用new,调用new会触发operator new,operator new调用开销大,因为导致底层操作系统的调用,系统调用的速度一般比进程内函数的调用速度慢,因此尽量少使用系统调用。
使用over-eager evaluation方法,其原因我们必须增大数组的尺寸以容纳索引i,那么根据相关性原则上我们就增加数组尺寸以在未来容纳比i大的其他索引:
template<class T>
T& DynArray<T>::operator[](int index)
{
if(index < 0)
throw an exception;
if(index > 当前最大的索引值)
int diff = index - 当前最大的索引值;
调用new分配足够的额外内存,以使得index+diff合法;
返回index位置上的数组元素;
}
这次内次分配内存是数组扩展所需求的两倍:
DynArray<double> a; //此时,只有a[0]是合法的数组
a[22] = 3.5; //调用new扩展,有效索引是0-44
a[32] = 0; //不用调用new扩展
贯穿本条款的一个常见主题:更快的速度需要更多的内存。cache运行结果需要更多的内存,但是一旦需要被缓存的结果时就能减少重新生成的时间。prefetch需要空间放置prefetch的东西,但是它减少了访问它们所需的时间。以空间换时间!
当你必须支持某些操作而不总需要的结果时,lazy evaluation是在这时候使用的用以提高程序的效率技术。当你必须支持某些操作而结果几乎总是被需要或不止一次的需要时,over-eager是这种时候用以提高程序效率的技术。它们所产生的巨大的性能是值得为这方面花费精力的。