我正在尝试找到一种方法来缓存我使用最近对算法(暂时暴力)的应用程序的元素数组。 根据{{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
总结一下:
提前致谢!
答案 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倍以上,并且代码已经被认为是“好”)。