over-eager evaluation(急速评估) 是一种将操作提前执行,以减少未来计算成本的优化技术。它有两种常见的实现方法:caching(高速缓存)和 prefetching(预先取出)。
caching
是一种缓存已经计算出来而有可能再被需要的值的方法。它的好处是,可以避免重复的计算,提高程序的效率。它的缺点是,需要额外的空间来存储缓存的值,而且可能会导致缓存的值过期或失效。
class Employee {
public:
// 根据员工姓名返回办公室编号,如果 map 不存在,就创建并填充它
int findCubicleNumber(const string& employeeName) const {
if (!cubicleMap) {
cubicleMap = new map<string, int>();
// 从数据库中读取所有员工的姓名和办公室编号,存入 map 中
...
}
// 在 map 中查找员工姓名,返回对应的办公室编号
auto it = cubicleMap->find(employeeName);
if (it != cubicleMap->end()) {
return it->second;
} else {
return -1; // 表示找不到该员工
}
}
...
private:
mutable map<string, int>* cubicleMap; // 员工姓名和办公室编号的映射
};
该策略就是使用一个局部缓存(local cache),将相对昂贵的“数据库查询动作”以相对价廉的“内存内数据结构查找动作”取代之。倘若我们假设房间号码频繁被需要,而且这是正确的假设,那么在 findCubicleNumber 内使用 cache, 应该可以降低“返回一个职员的房间号码”的平均成本。
prefetching
是一种预先取出未来可能会用到的值的方法。它的好处是,可以避免等待数据的传输,提高程序的效率。它的缺点是,需要额外的带宽来传输数据,而且可能会导致取出的数据没有用上或过早被替换。
template<class T>
class DynArray{...}; //template,用于元素类型为T的动态数组
DynArray<double> a; //此时,只有a[0]是合法的数组元素
a[22] = 3.5; //a被自动扩张,此时有效索引0-22
a[32] = 0; //a再度扩张自己,此刻a[0]-a[32]都有效
template<class T>
T& DynArray<T>::operator[](int index)
{
if(index < 0)
throw an exception; //负值索引无效
if(index > 当前最大的索引值)
{
调用new分配足够内存; //调用new会触发operator new
}
return 返回该位置上元素;
}
此做法是在每次需要增加数组大小时就调用 new,但是 new 会调用 operator new,而 operator new(以及 operator delete) 通常代价昂贵,因为它们通常会调用底层操作系统,而“系统调用”往往比“进程(process)内的函数调用”速度慢。所以我们应该尽可能不要采用系统调用。
此处我们可以使用 over-eager evaluation(超急评估)策略,理由是: 如果我们此刻必须增加数组大小以接纳索引 i,“locality of reference法则”建议我们未来或许还需再增加大小,以接纳比 i 稍大的索引。为避免第二次(预期中的)扩张所需要的内存分配成本,我们把 DynArray 的大小调整到比它目前所需的大小(使索引i 有效)再大一些,而我们希望未来的扩张落入我们此刻所增加的弹性范围内。例如, DynArray::operator[]可撰写为如下:
template<class T>
T& DynArray<T>::operator[](int index)
{
if(index < 0)
throw an exception;
if(index > 当前最大的索引值)
int diff = index - 当前最大的索引值;
调用new分配足够的额外内存,分配index+diff这么大的内存,相当于容量翻倍了;
return index位置上的元素;
}
DynArray<double> a;
a[22] = 3.5; //new 被调用以扩张a的空间,使能容纳索引44,a的逻辑大小为23
a[32] = 0; //a的逻辑大小改变,以允许a[32]存在,但没有在调用new
这两种技术的主要目的都是提前获取数据或计算结果,以便在实际需要时能够更快地访问。然而,它们的实现方式和应用场景有所不同。 Caching 更关注保存已经计算的结果,而 Prefetching 更注重在需要时提前加载数据。