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

Posted

技术标签:

【中文标题】cpumemory.pdf - 缓存优化矩阵乘法【英文标题】:cpumemory.pdf - cache optimized matrix multiplication 【发布时间】:2013-09-11 13:33:54 【问题描述】:

我正在阅读 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 个双精度值,它将完全 利用。但随后 Ulrich 继续说道:

继续这个想法,以有效地使用 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 按列。请解释一下 Ulrich 的意思。

【问题讨论】:

【参考方案1】:

缓存里面有三个矩阵:左输入、右输入和结果。

原始代码可以很好地访问左侧输入,因为它是行优先的,并且最内层循环递增 k,因此它沿着缓存行前进。第二个矩阵可以通过单次展开很好地访问,因为现在所有缓存行中的列在缓存行被驱逐之前使用..

问题是结果矩阵..它也是行主要的,但是缓存行由 j 索引,而不是 k.. 你是对的.. j 已经展开,所以它使用所有元素在结果矩阵中的缓存行上..所以第二次展开似乎没有任何收获..它所做的只是添加两个额外的缓存行..一个额外的左侧矩阵和一个额外的结果矩阵!它不会提高任何缓存行元素的覆盖率!

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

【讨论】:

以上是关于cpumemory.pdf - 缓存优化矩阵乘法的主要内容,如果未能解决你的问题,请参考以下文章

缓存友好的优化:面向对象的矩阵乘法和函数内平铺矩阵乘法

矩阵乘法优化之分块矩阵

详解矩阵乘法

编程练习矩阵乘法

min 与 + 运算转换成类似于矩阵乘法的推导过程

numpy 和 tensorflow 中的各种乘法(点乘和矩阵乘)