我有两个数组:A
N_A
个随机整数,B
N_B
和0
之间有(N_A - 1)
个随机整数。我使用B
中的数字作为以下循环中A
的索引:
for(i = 0; i < N_B; i++) {
sum += A[B[i]];
}
在Intel i7-3770上进行试验,N_A
= 2.5百万,N_B
= 6400万,此循环仅需0.62秒,相当于大约9纳秒的内存访问延迟。 / p>
由于此延迟太小,我想知道硬件预取器是否正在发挥作用。有人可以提供解释吗?
答案 0 :(得分:4)
HW预取程序可以查看您的第一级间接(B[i]
),因为这些元素是顺序的。它能够提前发出多个预取,因此您可以假设对B的平均访问会触及缓存(L1或L2)。但是,预取器无法预测随机地址(存储在B中的数据)并从A中预取正确的元素。您仍然必须在几乎所有A访问中执行内存访问(忽略由于重用而偶尔的幸运缓存命中())
您看到如此低延迟的原因是对A的访问是非序列化的,CPU可以同时访问A的多个元素,因此时间不仅仅是累积。实际上,您在此处测量内存BW,检查总体访问64M元素所需的时间,而不是内存延迟(访问单个元素所需的时间)。
CPU内存单元的合理“快照”应该显示几个未完成的请求 - 对B[i]
,B[i+64]
的一些访问,...(中间访问应该在每个请求获取时简单地合并一个64Byte行),所有这些都可能是反映i
未来值的预取,根据之前获取的A
元素与B
元素的随机访问混合。
要衡量延迟,您需要每次访问取决于前一次访问的结果,例如通过使A中每个元素的内容成为下一次访问的索引。
答案 1 :(得分:2)
CPU在指令流中向前充电,并将同时处理多个未完成的负载。流看起来像这样:
load b[0]
load a[b[0]]
add
loop code
load b[1]
load a[b[1]]
add
loop code
load b[1]
load a[b[1]]
add
loop code
...
迭代仅由循环代码序列化,循环代码快速运行。 所有加载可以并发运行。 Concurrency is just limited by how many loads the CPU can handle.
我怀疑你想要对随机,不可预测,序列化的内存负载进行基准测试。这在现代CPU上实际上非常难。尝试引入一个牢不可破的依赖链:
int lastLoad = 0;
for(i = 0; i < N_B; i++) {
var load = A[B[i] + (lastLoad & 1)]; //be sure to make A one element bigger
sum += load;
lastLoad = load;
}
这需要执行最后一次加载,直到可以计算下一次加载的地址。