【问题标题】:Does the hardware-prefetcher benefit in this memory access pattern?硬件预取器是否受益于这种内存访问模式?
【发布时间】:2014-03-15 13:25:10
【问题描述】:

我有两个数组:AN_A 随机整数和 BN_B 之间的随机整数 0(N_A - 1)。我在以下循环中使用B 中的数字作为A 的索引:

for(i = 0; i < N_B; i++) {
    sum += A[B[i]];
}

在 Intel i7-3770 上进行实验,N_A = 2.56 亿,N_B = 6400 万,此循环仅需 0.62 秒,对应的内存访问延迟约为 9 纳秒。

由于这个延迟太小,我想知道硬件预取器是否在起作用。有人可以解释一下吗?

【问题讨论】:

    标签: performance hardware ram prefetch


    【解决方案1】:

    硬件预取器可以看穿您的第一级间接 (B[i]),因为这些元素是连续的。它能够提前发出多个预取,因此您可以假设对 B 的平均访问会命中缓存(L1 或 L2)。但是,预取器无法预测随机地址(存储在 B 中的数据)并从 A 中预取正确的元素。您仍然必须在几乎所有对 A 的访问中执行内存访问(忽略由于重用而偶尔的幸运缓存命中行数)

    您看到如此低延迟的原因是对 A 的访问是非序列化的,CPU 可以同时访问 A 的多个元素,因此时间不只是累积。实际上,您在这里测量内存 BW,检查总体访问 64M 元素需要多长时间,而不是内存延迟(访问单个元素需要多长时间)。

    CPU 内存单元的合理“快照”应该显示几个未完成的请求 - 对 B[i]B[i+64] 的一些访问,...(中间访问应该简单地合并,因为每个请求获取一个 64 字节的行) ,所有这些都可能是反映 i 未来值的预取,并根据之前提取的 B 元素与对 A 元素的随机访问混合在一起。

    要测量延迟,您需要每次访问取决于前一个的结果,例如通过使 A 中每个元素的内容成为下一次访问的索引。

    【讨论】:

      【解决方案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;
      }
      

      这需要执行最后一次加载,直到可以计算下一次加载的地址。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-02
        • 2022-01-10
        • 1970-01-01
        • 2014-01-27
        • 1970-01-01
        • 2011-08-08
        • 1970-01-01
        相关资源
        最近更新 更多