cpumemory.pdf - 缓存优化的矩阵乘法

时间:2013-09-11 13:33:54

标签: caching memory optimization matrix cpu

我正在阅读cpumemory.pdf Ulrich Drepper和我无法理解关于优化的以下部分 第6.2.1章(第49-50页)中的矩阵乘法中的高速缓存访​​问:

显示了矩阵乘法的第一种天真方法:

for (i = 0; i < N; ++i)
    for (j = 0; j < N; ++j)
        for (k = 0; k < N; ++k)
            res[i][j] += mul1[i][k] * mul2[k][j];
按列访问

mul2,因此每列都会浪费一个缓存行。乌尔里希说:

  

当sizeof(double)为8时,这意味着,为了充分利用缓存行,   我们应该将中间循环展开8次。

为简洁起见,我只展开了2次中间循环。

for (i = 0; i < N; ++i)
    for (j = 0; j < N; j += 2)
        for (k = 0; k < N; ++k) {
            res[i][j+0] += mul1[i][k] * mul2[k][j+0];
            res[i][j+1] += mul1[i][k] * mul2[k][j+1];
        }

现在显而易见的是,如果缓存行的宽度为2倍,那么它将是完全的 利用。但随后乌尔里希继续说道:

  

继续这个想法,也有效地使用res矩阵,即   同时写8个结果,我们应该将外循环展开8次   好。

为简洁起见,我再次展开外环2次。

for (i = 0; i < N; i += 2)
    for (j = 0; j < N; j+=2)
        for (k = 0; k < N; ++k) {
            res[i+0][j+0] += mul1[i+0][k] * mul2[k][j+0];
            res[i+0][j+0] += mul1[i+0][k] * mul2[k][j+0];
            res[i+1][j+0] += mul1[i+1][k] * mul2[k][j+0];
            res[i+1][j+1] += mul1[i+1][k] * mul2[k][j+1];
        }

对我来说,它似乎比以前的版本更糟糕,因为现在可以访问mul1 按列。请解释乌尔里希的意思。

1 个答案:

答案 0 :(得分:1)

缓存中有三个矩阵:左输入,右输入和结果。

左输入被原始代码访问得很好,因为它是行主要的,最里面的循环递增k,所以它向下移动缓存行...第二个矩阵通过单次展开很好地访问,因为现在所有在缓存行被驱逐之前使用缓存行中的列。

问题是结果矩阵..它也是行主要的,但缓存行是用j索引的,而不是k ...你是对的.. j已经展开了,所以它使用了所有的元素在结果矩阵中的缓存行上..所以第二次展开似乎没有任何东西..它所做的只是添加两个额外的缓存行..左边的矩阵的额外和结果矩阵的额外!它不会改善任何缓存行元素的覆盖范围!

然而,确实会重复使用正确矩阵的缓存行两次..这会减少必须引入正确矩阵的行的总次数...并且它不会增加左和右的矩阵的次数。右边的矩阵缓存行将被引入..所以也许整个行的重用是优势来自的地方..我想问题是这是否被正确阻止到缓存大小,以及缓存的集合关联是什么..如果所有三个矩阵中的所有行都保留在缓存中,那么这没有任何优势..(但它不会使事情变得更糟!)