这似乎是一个奇怪的问题..
假设缓存行的大小为 64 字节。此外,假设 L1、L2、L3 具有相同的缓存行大小(this 帖子说 Intel Core i7 就是这种情况)。
内存中有两个对象A
、B
,它们的(物理)地址相距N个字节。为了简单起见,我们假设A
位于缓存边界上,即它的地址是64的整数倍。
1) 如果N
< 64,当A
被CPU读取时,B
也会被读入缓存。因此,如果需要 B
,并且缓存行尚未被驱逐,CPU 会在很短的时间内获取 B
。大家都很高兴。
2) 如果N
>> 64(即远大于64),当A
被CPU读取时,B
未被读取与 A
一起进入缓存行。所以我们说“CPU 不喜欢四处追逐指针”,这是避免堆分配的基于节点的数据结构(如 std::list
)的原因之一。
我的问题是,如果 N
> 64 但仍然很小,比如 N
= 70,换句话说,A
和 B
不适合在一个缓存行中,但相距不太远,当 A
被 CPU 加载时,会读取 B
所花费的时钟周期数与 N
远大于 64 时所花费的时钟周期数相同?
改写 - 当A
加载时,令t表示获取B
所用的时间,为t (N=70) 远小于或几乎等于 t(N=9999999)?
我问这个问题是因为我怀疑 t(N=70) 比 t(N=70) 小得多>=9999999),因为 CPU 缓存是分层的。
如果有定量研究就更好了。
最佳答案
至少有三个因素可以使 A 丢失后获取 B 的速度更快。首先,处理器可以推测性地获取下一个 block (独立于任何基于步幅的预取引擎,这将取决于在时间和位置上彼此接近地遇到的两次未命中以确定步幅;单位步幅预取不需要确定步幅值[它是一个]并且可以在第一次错过之后开始)。由于这种预取会消耗内存带宽和片上存储,因此它通常具有节流机制(可以很简单,例如具有适度大小的预取缓冲区,并且仅在内存接口(interface)足够空闲时才进行高度推测性预取)。
其次,由于 DRAM 被组织成行,并且更改行(在单个存储体内)会增加延迟,如果 B 与 A 位于同一 DRAM 行中,则对 B 的访问可能会避免行预充电的延迟(以关闭先前打开的行)并激活(以打开新行)。 (这也可以提高内存带宽利用率。)
第三,如果B与A位于同一地址转换页中,则可以避免TLB。 (在许多设计中,分层页表遍历在附近区域也更快,因为可以缓存分页结构。例如,在 x86-64 中,如果 B 与 A 位于同一 2MiB 区域中,则 TLB 未命中可能只需执行一次内存访问因为页目录可能仍被缓存;此外,如果 B 的转换与 A 的转换位于同一 64 字节缓存行中,并且 A 的 TLB 未命中是最近发生的,则缓存行可能仍然存在。)
在某些情况下,我们还可以通过将可能丢失的对象排列在固定、有序的步幅中来利用基于步幅的预取引擎。这似乎是一个相当困难且有限的上下文优化。
跨步增加延迟的一个明显方式是引入冲突未命中。大多数缓存使用简单的模二幂索引,且关联性有限,因此二步长幂(或到同一缓存集的其他映射)可能会将不成比例的数据量放置在有限数量的集合中。一旦超过关联性,就会发生冲突遗漏。 (已提出倾斜关联性和非二次幂模索引来减少此问题,但这些技术尚未得到广泛采用。)
(顺便说一下,指针追踪特别慢的原因不仅仅是空间局部性低,而是在对 A 的访问完成之后才能开始对 B 的访问,因为存在数据依赖性,即获取B的延迟不能与获取A的延迟重叠。)
关于caching - CPU缓存: does the distance between two address needs to be smaller than 8 bytes to have cache advantage?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45720624/