处理器请求从内存获取一组字节时,最终获得的不仅仅是这些字节。从内存中提取数据时,数据连同其相邻字节作为一条缓存线一起提取,如图1-12所示。根据系统中处理器的不同,缓存线可小至16字节,也可大至128字节或更多。缓存线的典型大小为64字节。缓存线总是对齐的,所以64字节缓存线的起始地址是64的倍数。因为这种设计决策优化了系统,使系统总是传递64字节大小、对齐的数据,所以简化了系统;另一种设计决策则采用远为复杂的内存接口,不得不处理不同尺寸的内存块和未对齐的起始地址。 从内存中提取的数据行存储于缓存中。缓存能提高性能,因为处理器很可能重复使用该数据或访问存储于同一缓存线中的数据。通常有指令缓存和数据缓存,也有多级缓存。 存在多级缓存的原因是:缓存越大,确定缓存中是否存在某个数据项的时间就越长。一个处理器可能有一个只需几个时钟周期就能访问、较小的一级缓存,以及要用几十个时钟周期来访问、大得多的二级缓存。这两者都比内存快得多,因为访问内存可能需要数百个时钟周期。从内存或从某级缓存获取数据项所花费的时间称为内存或某级缓存的延迟。图1-13所示为一个典型的内存层次结构。 主内存访问延迟越大,采用多级缓存就获益越大。有些系统甚至享有三级缓存。 缓存有两个明显特性:缓存线大小和缓存大小。缓存中的缓存线数目可通过缓存大小除以缓存线大小算出。例如,对于缓存线大小为64字节的4 KB缓存,缓存线数目为64条。 缓存还有一些不太容易发现、对应用程序性能的直接影响不那么明显的其他特性,其中一个值得一提的特性就是“关联性”(associativity)。在一个简单缓存中,内存中的每条缓存线都会映射到缓存中的一个位置,这就是所谓的直接映射缓存。如以前面所述的4 KB简单缓存为例,则内存中相隔4 KB的缓存线都会映射到同一缓存线上,如图1-14所示。 显然,一个程序访问内存的跨度为4 KB时, 最终只能使用缓存中的一个条目,如该程序需要同时使用多条缓存线,则可能受制于较低的性能。 这个问题的解决方法是增加缓存的关联性,也就是让内存中的一条缓存线可映射到缓存中的多个位置,从而降低缓存冲突的可能性。在两路关联缓存中,内存的每条缓存线可以映射到缓存中的两个位置之一,最终映射到哪个位置根据某个替换策略来选择,可以是随意替换,也可以采用最近最少使用替换策略,即取决于两个位置中哪一个包含的数据较早。内存行映射到缓存的可能位置数加倍,意味着映射到同一缓存线的内存行之间的间隔减半,但总体来说,这种变化将导致更有效地利用缓存,减少缓存未命中数。图1-15显示了这种变化。 完全关联缓存是指内存中的任何地址都能映射到任一缓存线,采用这种方法很可能会使缓存未命中率最低,但其实现方式却最为复杂,因而实际上很少实现。 在多个线程共享一个缓存级别的系统中,缓存有较高的关联性就更为重要。想了解为何如此,请设想一下,同一应用程序的两份副本共享一个共同的直接映射缓存。如果每个副本访问同一虚拟内存地址,那么两者都将试图使用同一缓存线,这种情况下只有一个会成功。遗憾的是,这种成功将是短暂的,因为另一副本立即会把这一缓存线中的数据换为自己需要的数据。