优化四:非阻塞缓存,提高缓存带宽
对于允许乱序执行的流水线计算机,处理器不需要因数据高速缓存未命中而停止。 例如,处理器可以继续从指令高速缓存获取指令,同时等待数据高速缓存返回丢失的数据。 非阻塞高速缓存或无锁高速缓存允许数据高速缓存在未命中期间继续提供高速缓存命中,从而增强了这种方案的潜在优势。 这种“未命中下的命中”优化通过在未命中期间提供帮助而不是忽略处理器的请求来减少有效的未命中损失。 一个微妙而复杂的选项是,如果缓存可以重叠多个未命中,则可以进一步降低有效未命中惩罚:“多次未命中下的命中”或“未命中下的未命中”优化。 仅当内存系统可以处理多次未命中时,第二个选项才有用。 大多数高性能处理器(例如英特尔酷睿处理器)通常都支持两者,而许多低端处理器仅在 L2 中提供有限的非阻塞支持。
简单来说,非阻塞缓存是一种数据缓存,它可以在处理一个缓存不命中的请求时,继续响应其他缓存命中的请求,从而减少处理器的等待时间。
非阻塞缓存有以下几种优化方式:
- 命中在不命中下(hit under miss):一种非阻塞缓存优化方式,它允许在处理一个缓存不命中的请求时,同时响应一个缓存命中的请求。
- 命中在多重不命中下(hit under multiple miss)或不命中在不命中下(miss under miss):一种非阻塞缓存优化方式,它允许在处理多个缓存不命中的请求时,同时响应多个缓存命中或不命中的请求。这种方式需要内存系统能够同时服务多个不命中的请求。
- Farkas和Jouppi (1994) 的研究:他们假设了一个8 KiB的单层次缓存和一个14周期的不命中惩罚,并观察了允许一个命中在不命中下优化时,对于SPECINT92和SPECFP92基准测试程序的有效不命中惩罚(effective miss penalty)的减少情况。他们发现对于SPECINT92程序平均减少了20%,对于SPECFP92程序平均减少了30%。
- Li等人 (2011) 的研究:他们更新了前一项研究,使用了一个多层次缓存、更现代化的不命中惩罚假设和更大更具挑战性的SPECCPU2006基准测试程序。他们的研究基于一个类似于Intel i7的单核处理器模型。图显示了允许1、2和64个命中在不命中下优化时,数据缓存访问延迟的减少情况。他们发现由于缓存变大和增加了三级缓存,非阻塞缓存的效果有所降低,对于SPECINT2006程序平均减少了约9%,对于SPECFP2006程序平均减少了约12.5%。
评估非阻塞缓存性能的难点是,缓存不命中并不一定会导致处理器停顿。在这种情况下,很难判断任何一个不命中的影响,从而计算平均内存访问时间。有效的不命中惩罚并不是所有不命中的时间之和,而是处理器停顿的不重叠的时间。非阻塞缓存的效益是复杂的,它取决于以下几个因素:
- 当有多个不命中时,不命中惩罚的大小,这决定了处理器能否在等待数据时继续执行其他指令。
- 内存引用模式,这决定了处理器在一个不命中期间需要访问多少其他数据。
- 处理器能够同时处理多少个不命中的请求,这决定了非阻塞缓存能够提供多少优化方式。
一般来说,乱序执行的处理器能够隐藏大部分一级数据缓存不命中但在二级缓存命中的惩罚,但是不能隐藏很大一部分低层次缓存不命中的惩罚。决定支持多少个未完成的不命中请求取决于以下几个因素:
- 不命中流中的时间和空间局部性,这决定了一个不命中是否可以启动一个新的访问到低层次缓存或内存。
- 响应内存或缓存的带宽,这决定了一个不命中需要等待多长时间才能得到数据。
- 要允许在最低层次缓存有更多的未完成的不命中请求(因为这里的不命中时间最长),就需要在高层次缓存至少支持同样多的不命中请求,因为一个不命中必须从最高层次缓存开始。
- 内存系统的延迟,这决定了一个不命中需要等待多长时间才能得到响应。
实现非阻塞缓存有以下两个主要的问题:
- 如何在命中和不命中之间进行仲裁,解决冲突。在一个阻塞缓存中,不命中会导致处理器停顿,不会有更多的访问发生,直到不命中处理完毕。在一个非阻塞缓存中,命中可能与从下一级存储器返回的不命中发生碰撞。如果允许多个未完成的不命中,这种碰撞就更有可能发生。这些碰撞必须被解决,通常的方法是先给予命中优先于不命中,其次是对碰撞的不命中进行排序(如果可能的话)。
- 如何跟踪未完成的不命中,知道何时可以让加载或存储继续执行。在一个阻塞缓存中,我们总是知道哪个不命中正在返回,因为只有一个可以未完成。在一个非阻塞缓存中,这种情况很少成立。考虑一下在一级缓存发生的一个不命中。它可能在二级缓存产生一个命中或不命中;如果二级缓存也是非阻塞的,那么返回到一级缓存的不命中的顺序就不一定和它们最初发生的顺序相同。多核和其他多处理器系统由于有非均匀缓存访问时间也会引入这种复杂性。当一个不命中返回时,处理器必须知道哪个加载或存储导致了这个不命中,以便这条指令可以继续执行;并且必须知道数据应该放在缓存的哪个位置(以及这个块的标记位的设置)。
在非阻塞高速缓存中,命中可能与从内存层次结构的下一级返回的未命中发生冲突。如果我们允许多个未完成的未命中,则未命中甚至有可能发生冲突。
为什么?
因为非阻塞缓存需要在有限的缓存空间和带宽内处理多个请求,而这些请求可能访问相同或相邻的缓存块。例如:
- 如果一个命中请求和一个未命中请求访问同一个缓存块,那么它们就会发生冲突,因为它们需要同时读写这个块。通常的解决方法是让命中请求优先于未命中请求,以减少处理器的等待时间。
- 如果一个未命中请求和另一个未命中请求访问同一个缓存组(set),那么它们就会发生冲突,因为它们需要同时占用这个组的一个空闲位置。通常的解决方法是对未命中请求进行排序,以保证一定的顺序和公平性。
- 如果多个未命中请求返回的数据量超过了缓存到处理器的总线带宽,那么它们就会发生冲突,因为它们需要同时传输数据给处理器。通常的解决方法是增加总线带宽或使用压缩技术,以减少数据量。
非阻塞缓存需要额外的逻辑,因此在能量上有一些代价。然而,很难准确地评估它们的能量消耗,因为它们可能减少停顿时间,从而降低执行时间和能量消耗。除了上述问题之外,多处理器内存系统(无论是在单个芯片上还是多个芯片上)还必须处理与内存一致性和一致性相关的复杂实现问题。而且由于缓存不命中不再是原子性的(因为请求和响应被分开并且可能被多个请求交错),还有死锁的可能性。