如何确定缓存的阻塞因子

时间:2013-04-29 09:22:36

标签: c++ c caching blocking cache-control

我正在尝试找到一种方法来缓存我使用最近对算法(暂时暴力)的应用程序的元素数组。 根据{{​​3}}论文,它说:

  

阻塞是一种增加优势的通用优化技术   内存层次结构的有效性。通过更快地重用数据   层次结构的级别,它减少了平均访问延迟。它   也减少了对较慢级别的引用数量   层次结构。因此,阻塞优于诸如的优化   预取,隐藏延迟但不减少内存   带宽要求。这种减少尤为重要   多处理器因为内存带宽往往是瓶颈所在   系统。已经证明阻塞对于许多算法是有用的   线性代数。

本文给出了矩阵乘法代码,并将其修改为阻塞,减少了缓存未命中:

for kk = 1 to N by B
  for j = 1 to N by B
    for i = 1 to N
      for k = kk to min(kk + B-1, N)
         r = X[i,k];  // register allocated
         for j = jj to min(jj + B-1, N)
           Z[i,j] += r * Y[k,j];

此处, B 阻止系数,但我们如何确定?是否有通用的方法来查找cpu缓存可以处理的特定限制?可能并非所有cpu都具有相同的缓存。通用程序说:

  • 首先是重构代码以阻止那些带有重用的循环。
  • 其次是选择最大化地点的阻塞因素。

最接近的对算法(暴力)是:

minDist = infinity
for i = 1 to length(P) - 1
  for j = i + 1 to length(P)
    let p = P[i], q = P[j]
    if dist(p, q) < minDist:
      minDist = dist(p, q)
      closestPair = (p, q)
return closestPair

总结一下:

  • 如何优化B因子而无需手动测试特定cpu缓存大小的限制。有没有办法使用C语言返回当前可用的缓存?
  • 如何优化使用1D阵列的最近对算法?我的意思是,应该存储和重用哪些元素,因为它包含x,y坐标的一维结构元素数组,每个点都必须与所有其他点进行比较(让我们坚持使用强力算法)。

提前致谢!

1 个答案:

答案 0 :(得分:4)

第一个问题:

没有简单的方法可以确定 B ,而无需在要优化的机器上进行实际测试。话虽如此,你可以通过一些实验找到一些“对大多数系统都有好处”的数字(我在12-15年前对这种事情做了很多工作),我发现使用了大约8-16KB块的工作很好。 “刚刚运行所有内存”和“通过块运行”之间的效果非常显着,如果你从非常小的块开始,你可以看到一些很大的改进,因为你开始变大。然后“返回”下降,直到你达到 B 那么大以至于你回到你开始的地方的水平(抛出好的缓存以获取你之前不会使用的其他东西)被抛弃了)。

我很确定如果您为代码选择 B 的“大小”,并测试您获得的性能,并且如果您绘制图表,那么您可能如果您绘制“花费时间”(或者倒置浴缸,如果您绘制“每单位时间处理的项目数”),则发现它看起来像“浴缸”。只需在浴缸的“扁平”部分找到一些点。然而,请在几台不同的机器上进行尝试,以确保您在所有(或至少大多数)机器上处于“平坦位”。

对于你的第二个问题,如下:

minDist = infinity
for i = 1 to length(P) - 1 by B
  for j = i + 1 to length(P) by B
    for ib = i to i+B-1
      for jb = j to j+B-1
       let p = P[ib], q = P[jb]
       if dist(p, q) < minDist:
         minDist = dist(p, q)
         closestPair = (p, q)
return closestPair

如果length(P)不是 B 的倍数,那么处理最后几个元素会有一些额外的工作,所以{{1}中不是i+B-1对于ib循环,您可能需要max(length(P), i+B-1)和类似的循环。

编辑:

缓存本身会决定缓存中保留哪些数据,而且您可以做很少的事情来改变这里发生的事情。您可以更改的是您正在处理的数据块。

“阻止”的关键是保留(L1)缓存中正在处理的数据。

假设整个数据锁是100000个元素,每个4字节,所以大约400KB。这将不适合任何现代处理器的L1缓存,因为它最多为64KB,通常为32KB。因此,当我们使用jb循环遍历项时,i循环将通过加载数组的后续部分来“抛弃”所有良好的L1缓存内容。当然,当j循环在下一次开始时,缓存中当前没有任何数据是有用的,因为它是数组的所有高索引。

如果我们一次只通过一小部分数组,我们可以在每个循环中通过一个 B 大小的数组 - 其中 B < / strong>元素不会占用超出缓存的空间。因此j循环不会丢弃jb循环的数据(反之亦然)。这意味着每个内部循环运行得更快(我已经看到执行速度提高了3倍以上,并且代码已经被认为是“好”)。