所有的现代处理器都包含一个或多个高速缓存(cache)存储器,以对这样少量的存储器提供快速的访问。这里我们只考虑所有的数据都存放在高速缓存中的情况。
我们知道,现代处理器有专门的功能单元来执行加载和存储操作,这些单元有内部的缓冲区来保存未完成的内存操作请求集合。
一.加载的性能
一个包含加载操作的程序的性能既依赖于流水线的能力,也依赖于加载单元的延迟。对于任何数据类型的组合和合并操作来说,CPE从来没有到过0.50以下,一个制约CPE的因素是,对于每个被计算的元素,所有的示例都需要从内存读一个值。对两个加载单元而言,其每个时钟周期只能启动一条加载操作,所以CPE不可能小于0.50。对于每个被计算的元素必须加载k个值的应用,我们不可能获得低于k/2的CPE。
二.存储的性能
存储操作并不影响任何寄存器值。因此,就齐本性来说,一系列存储操作不会产生数据相关。只有加载操作会受存储操作结果的影响,因为只有加载操作能由存储操作写的那个位置读回值。下面的函数write_read说明了加载和存储操作之间可能的相互影响
/* Write to dest, read from src */
void write_read(long *src, long *dst, long n)
{
long cnt = n;
long val = 0;
while(cnt){
*dst = val ;
val = (*src)+1;
cnt--;
}
}
在示例A中,参数src是一个指向数组元素a[0]的指针,而dest是一个指向数组元素a[1]的指针。在此种情况中,指针引用src的每次加载都会得到值-10.因此,在两次迭代之后,数组元素就会分别保持固定为-10和-9。从src读出的结果不受对dest的写的影响。在较大次数的迭代上测试这个示例得到CPE等于1.3。
在示例B中,参数src和dest都是指向数组元素a[0]的指针。在这种情况中,指针引用src的每次加载都会得到指针引用*dest的前次执行存储的值。因而,一系列不断增加的值会被存储在这个位置。通常,如果调用函数write_read时参数src和dest指向同一个内存位置,而参数cnt的值为n>0,那么净效果是将这个位置设置为n-1。我们称这种现象为写/读相关——一个内存读的结果依赖于一个最近的内存写。我们的性能测试表明示例B的CPE为7.3。
存储单元包含一个存储缓冲区,它包含已经被发射到存储单元而又还没有完成的存储操作的地址和数据,这里的完成包括更新数据高速缓存。提供这样一个缓冲区,使得一系列存储操作不必等待每个操作都更新高速缓存就能够执行。当一个加载操作发生时,它必须检查存储缓冲区中的条目,看有没有地址相匹配。如果有地址相匹配(在写的字节与在读的字节有相同的地址)它就取出相应的数据条目作为加载操作的结果。
s_addr指令计算存储操作的地址,在存储缓冲区创建一个条目,并且设置该条目的地址字段,s_data操作设置该条目的数据字段。两个计算是独立执行的,特别地,s_addr操作的地址计算必须在s_data操作之前。并且load操作必须检查所有未完成的存储操作的地址,如果s_data和load操作的两个地址相同,load操作必须等待直到s_data将它的结果存放到存储缓冲区中,但是如果两个地址不同,两个操作就可以独立地进行。
对于寄存器操作,在指令被译码成操作的时候,处理器就可以确定哪些指令会影响其他哪些指令。另一方面,对于内存操作,只有到加载和存储的地址被计算出来以后,处理器才能确定哪些指令会影响其他的哪些。