本节书摘来自异步社区《现代体系结构上的UNIX系统:内核程序员的对称多处理和缓存技术(修订版)》一书中的第2章,第2.11节,作者:【美】Curt Schimmel著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.11 高速缓存的性能
虽然全面讨论高速缓存的性能超出了本书的范围,但还是可以做以下一些观察。首先,高速缓存的性能不仅取决于高速缓存的设计,而且取决于应用程序的引用模式。因此,必须谨慎地通过测试基准程序来判定高速缓存的性能以及一般化测试结果。虽然很容易编写一个获得100%命中率的基准程序,但是把它们运用到实际应用中的时候,这样的结果是毫无意义的。例如,下面的程序会获得100%的高速缓存命中率:
while (1)
;
循环执行一次之后,所有的指令引用都会在高速缓存中命中。相反,下面的代码片段给一个数组中的每个元素都乘以常数c,因而会得到相当低的高速缓存命中率(假定数组要比高速缓存大)。
for ( j=0; j < YMAX; j++)
for ( i=0; i < XMAX; i++)
matrix[i][j] *= c;
因为在C语言中,数组是按行来保存的,在使用高速缓存的时候,如果数组中一行的大小超过了高速缓存行的长度(假定一开始数组没有被高速缓存),那么每次执行最里面的语句就会出现一次缺失。这样的情形尤其糟糕,因为行很长的高速缓存会读取大量从来都不会用到的数据。互换两层for循环会因为空间局部性而提高性能。即使每个元素只读取一次,高速缓存每次都要读取整整一行的事实也意味着引用连续的元素可能会产生一次命中。例外的情况是那些行很小的高速缓存,像MIPS R2000/R3000,它们的每个高速缓存行只有4字节。如果数组matrix的每个元素也是4字节,那么就没有空间局部性的好处了。
即使高速缓存的性能是依赖于应用的,在直觉上还是可以有下面的结论(虽然对于所有应用来说,它们并不一定都对,但是对于包括典型UNIX命令在内的许多应用来说,它们都是正确的)。首先,写回策略比写直通策略更可取,因为程序一般会因为时间局部性而多次修改变量。即使它们没有多次修改变量,写回高速缓存机制也往往不会增加任何性能开销,因为写直通一行或者在以后替换行的时候再写回都要花费一个存储器周期。为每一行维护一个修改位并处理写回虽然增加了复杂性,但这样做是值得的。在最差的情况下,没有时间或者空间局部性可言,有写分配能力的写回策略只会多读一次高速缓存行。完全没有局部性的情形是非常少见的,所以它们不会对性能造成明显的影响。
接下来,增加组的大小一般也会有帮助。对于小规模的高速缓存(1 KB或者更小)来说这样的做法特别有用,因为即便多个地址产生了相同的索引,它也能利用更多的高速缓存。对于非常大的高速缓存(1 MB或者更多)来说,增加组的大小就没那么重要了,因为随着行数的增加,出现一段数据替换现有的高速缓存数据的可能性也逐渐减小。
由于空间局部性,增加高速缓存行的大小一般也会对高速缓存性能有帮助。高速缓存行太长的缺点是在缺失处理期间读取数据需要开销。在小规模高速缓存的组织结构中找不到很长的高速缓存行,因为这就意味着高速缓存中的行数会更少。因此出现替换的频率就更高了。
高速缓存的性能也会受到操作系统的影响。不同的高速缓存组织结构需要不同情况的冲洗机制。有若干种技术可以用来减少必须发生的冲洗量。这很重要,因为频繁的冲洗很花时间,并且减少了有用的数据被高速缓存的时间。