我在OpenMP中运行此代码进行矩阵乘法,我测量了它的结果:
#pragma omp for schedule(static)
for (int j = 0; j < COLUMNS; j++)
for (int k = 0; k < COLUMNS; k++)
for (int i = 0; i < ROWS; i++)
matrix_r[i][j] += matrix_a[i][k] * matrix_b[k][j];
根据我放置#pragma omp
指令的位置 - 在j循环,k循环或i循环之前,有不同版本的代码。此外,对于每个变体,我为默认静态调度运行了不同的版本,使用块1和10运行静态调度,使用相同的块运行动态调度。我还测量了CodeXL中的DC访问,DC未命中,CPU时钟,退役指令和其他性能指标的数量。以下是AMD Phenom I X4 945上尺寸为1000x1000的矩阵的结果:
Results of performance measurements
multiply_matrices_1_dynamic_1
在第一个循环之前是#pragma omp
的函数,在第一个循环之前是动态调度,等等。以下是我对结果不太了解的一些事情,并希望得到帮助:< / p>
另外,我对TLB未命中缓存未命中的关系感到困惑。什么时候使用DTLB?我教授的文档说每次DC访问都是DTLB请求,但我不明白它是如何工作的 - TLB未命中数通常大于DC访问次数。如何计算TLB未命中率?我的教授说这是TBL未命中/ DC访问。他还说我可以通过缓存命中率和空间位置按TLB命中率来计算时间局部性。这是如何工作的?
答案 0 :(得分:2)
Gilles有正确的想法,你的代码缓存不友好,但他的解决方案仍然has a similar problem because it does the reduction over k
on matrix_b[k][j]
。
一种解决方案是计算matrix_b
的转置,然后您可以matrix_bT[j][k]
超过k
,这是缓存友好的。转置为O(n^2))
,矩阵乘法为O(n^3)
,因此转置的成本为1/n
。即对于较大的n
,它变得可以忽略不计。
但是使用转置比使用转置更容易。像这样减少j
:
#pragma omp for schedule(static)
for (int i = 0; i < ROWS; i++ ) {
for (int k = 0; k < COLUMNS; k++ ) {
for ( int j = 0; j < COLUMNS; j++ ) {
matrix_r[i][j] += matrix_a[i][k]*matrix_b[k][j];
}
}
}
吉勒&#39;每次迭代时,方法需要从内存中读取两次,而这种解决方案每次迭代需要两次读取和一次内存写入,但它具有更多的缓存友好性,这足以弥补对内存的写入。
答案 1 :(得分:1)
我不确定你的数字显示的是什么,但我确信你的代码,就像现在写的那样,几乎一样无效。因此,在你使代码合理有效之前,讨论关于这个或那个计数器数字的细节将毫无意义。
我声称您的代码无效的原因是因为您组织循环的顺序可能是最糟糕的:对数据的访问都不是线性的,导致缓存的使用效率极低。通过简单地围绕循环进行交换,您应该显着提高性能,并开始考虑可以做些什么来进一步改进它。
例如,这个版本应该已经好多了(未经测试):
#pragma omp for schedule( static )
for ( int i = 0; i < ROWS; i++ ) {
for ( int j = 0; j < COLUMNS; j++ ) {
auto res = matrix_r[i][j]; // IDK the type here
#pragma omp simd reduction( + : res )
for ( int k = 0; k < COLUMNS; k++ ) {
res += matrix_a[i][k] * matrix_b[k][j];
}
matrix_r[i][j] = res;
}
}
(注意:我添加simd
指令只是因为它看起来合适,但这不是重点:
从那里开始,尝试循环折叠,线程调度和/或循环平铺将开始有意义。