矩阵乘法速度问题

时间:2016-12-20 19:59:36

标签: c++ performance caching matrix

我正在调查缓存未命中如何影响计算速度。我知道有很多算法可以更好地将两个矩阵相乘(即使下面两个循环的简单交换也会有帮助),但请考虑以下代码:

float a[N][N];
float b[N][N];
float c[N][N];
// ...
{
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            float sum = 0.0;
            for (int k = 0; k < N; k++) {
                sum = sum + a[i][k] * b[k][j];
            }
            c[i][j] = sum;
        }
    }
}

我已经为N的许多值重新编译了此代码,并测量了运行它的时间。我希望在N=1250左右突然增加时间,此时矩阵c不再适合缓存(c的大小则为1250*1250*sizeof(float)=6250000,或大约6MB ,这是我的L3缓存的大小。)

事实上,总体趋势是,在此之后,平均时间与之前推断的时间相比大致为三倍。但N%8的价值似乎对结果产生了巨大影响。例如:

1601 - 11.237548
1602 - 7.679103
1603 - 12.216982
1604 - 6.283644
1605 - 11.360517
1606 - 7.486021
1607 - 11.292025
1608 - 5.794537
1609 - 11.469469
1610 - 7.581660
1611 - 11.367203
1612 - 6.126014
1613 - 11.730543
1614 - 7.632121
1615 - 11.773091
1616 - 5.778463
1617 - 11.556687
1618 - 7.682941
1619 - 11.576068
1620 - 6.273122
1621 - 11.635411
1622 - 7.804220
1623 - 12.053517
1624 - 6.008985

有一段时间,我认为那些可能是对齐问题 - 任何矩阵的行在N%8==0时对齐为32个字节(第一个问题 - 为什么特别是32个字节?SSE指令,例如movaps可以处理16B对齐数据。)

另一个想法是,这可能以某种方式连接到缓存关联性(对于L1和L2为8路,对于我的机器为L3的12路)。

但后来我注意到N的某些值,例如1536,会出现意外的峰值(即使在这些情况下对齐应该很好 - 1536==256*6,关联性也不是问题 - 1536==128*12==192*8)。例如:

1504 - 4.644781
1512 - 4.794254
1520 - 4.768555
1528 - 4.884714
1536 - 7.949040
1544 - 5.162613
1552 - 5.083331
1560 - 5.388706

时序非常一致,因此处理器负载的峰值不是问题。我在打开优化(-O2)的情况下编译代码。不幸的是,我的想法已经不多了。这种行为可能是什么原因?

1 个答案:

答案 0 :(得分:0)

对您的示例最重要的 - CPU缓存行大小。对于CPU,它通常是64字节。即使您的程序读取或写入1个字节,CPU也会对所有行(64字节)进行读/写操作。这就是为什么,如果你的程序达到缓存行,你的表现就会很好。如果它遗漏,则读/写内存会有额外的开销。 L3缓存的大小并不那么重要。

代码

// all your stack variables are good. Compiler will optimize them well. 
for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
        float sum = 0.0;
        for (int k = 0; k < N; k++) {
            sum = sum + 
                  a[i][k] *  // here you are good, you read memory sequentially 
                  b[k][j]; // here, you are not good, every read comes from different cache line
        }
        c[i][j] = sum; // here doesn't matter, it is rare operation
    }
}

与您的案例is here类似。这个演示文稿解释了如何优化这些代码以及它为什么以这种方式工作。我希望你能找到你需要的一切。

image

相关问题