如何确定缓存的阻塞因子

How to determine blocking factor for caching

本文关键字:何确定 缓存      更新时间:2023-10-16

我正试图找到一种方法来缓存我的元素数组的应用程序,使用最接近对算法(目前蛮力)。根据阻塞算法的缓存性能和优化论文说:

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

本文给出了一个矩阵乘法代码,并将其修改为阻塞,减少了缓存缺失:

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坐标的结构元素的1D数组,每个点都必须与所有其他点进行比较(让坚持蛮力算法)。

提前感谢!

第一个问题

没有简单的方法可以确定B,而不是在您打算优化的机器上进行实际测试。话虽如此,您可以通过一些实验找到一些"适合大多数系统"的数字(我在大约12-15年前从事过这类工作),我发现使用大约8-16KB的块效果非常好。"一有内存就遍历"answers"遍历块"之间的效果是非常显著的,如果你从很小的块开始,你可以看到一些很大的改善。然后"返回"下降,直到你达到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的倍数,则有一些额外的工作来处理最后几个元素,因此ib循环中的i+B-1可能需要max(length(P), i+B-1)和类似的jb循环。

编辑:

缓存将自己决定哪些数据保存在缓存中,您几乎无法改变这里发生的事情。您可以更改的是正在处理的数据块。

"阻塞"的关键是将正在处理的数据保存在(L1)缓存中。

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

如果我们以块为单位一次遍历数组的一小部分,则可以在每个循环中遍历数组B大小的平方,其中B元素占用的空间不会超过缓存的容量。因此,jb循环不会为ib循环抛出数据(反之亦然)。这意味着每个内部循环的运行速度都要快得多(我见过执行速度快3倍以上的代码,而且这是在已经被认为是"好的"的代码上)。