for(i = 0; i < dimension; i++)
for(j = 0; j < dimension; j++)
for(k = 0; k < dimension; k++)
C[dimension*i+j] += A[dimension*i+k] * B[dimension*k+j];
这里,矩阵的大小由维度表示。
现在,如果矩阵的大小是2000,运行这段代码需要147秒,而如果矩阵的大小是2048,则需要447秒。所以虽然不同。的乘法是(2048 * 2048 * 2048)/(2000 * 2000 * 2000)= 1.073,时间差是447/147 = 3。有人可以解释为什么会发生这种情况吗?我预计它会线性扩展,这不会发生。我不是想做最快的矩阵乘法代码,只是试图理解为什么会发生。
规格:AMD Opteron双核节点(2.2GHz),2G RAM,gcc v 4.5.0
程序编译为gcc -O3 simple.c
我也在英特尔的icc编译器上运行,并看到类似的结果。
编辑:
正如在意见/答案建议,我运行代码与维度= 2060,它需要145秒。
完整的程序:
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
/* change dimension size as needed */
const int dimension = 2048;
struct timeval tv;
double timestamp()
{
double t;
gettimeofday(&tv, NULL);
t = tv.tv_sec + (tv.tv_usec/1000000.0);
return t;
}
int main(int argc, char *argv[])
{
int i, j, k;
double *A, *B, *C, start, end;
A = (double*)malloc(dimension*dimension*sizeof(double));
B = (double*)malloc(dimension*dimension*sizeof(double));
C = (double*)malloc(dimension*dimension*sizeof(double));
srand(292);
for(i = 0; i < dimension; i++)
for(j = 0; j < dimension; j++)
{
A[dimension*i+j] = (rand()/(RAND_MAX + 1.0));
B[dimension*i+j] = (rand()/(RAND_MAX + 1.0));
C[dimension*i+j] = 0.0;
}
start = timestamp();
for(i = 0; i < dimension; i++)
for(j = 0; j < dimension; j++)
for(k = 0; k < dimension; k++)
C[dimension*i+j] += A[dimension*i+k] *
B[dimension*k+j];
end = timestamp();
printf("\nsecs:%f\n", end-start);
free(A);
free(B);
free(C);
return 0;
}
它可能是你可以适应2行2000双打入缓存。其中slighly小于32kb的L1缓存。 (同时留下房间其他必要的东西)
但是当你把它提高到2048,它使用整个缓存(和你溢出一些,因为你需要空间的其他东西)
假设高速缓存策略是LRU,溢出高速缓存只是一个很小的位将导致整个行被重复刷新并重新加载到L1高速缓存。
另一种可能性是由于二的幂的高速缓存关联性。虽然我认为处理器是双向L1关联,所以我不认为这在这种情况下很重要。 (但我会把想法还有)
可能的解释2:由于L2缓存上的超级对齐导致冲突缓存未命中。
您的B数组正在列上迭代。所以访问是大步。您的总数据大小为2k x 2k,每个矩阵大约为32 MB。这比你的L2缓存大得多。
当数据不完全对齐时,您将在B上具有体面的空间局部性。虽然您跳跃行,并且每个缓存行仅使用一个元素,缓存线保留在L2缓存中,以由中间循环的下一次迭代重用。
然而,当数据完全对齐(2048)时,这些跳跃将全部落在相同的“缓存路”上,并且将远远超过L2缓存相关性。因此,所访问的B的高速缓存行不会停留在下一次迭代的高速缓存中。相反,他们需要从ram拉一路。
相关文章
转载注明原文:矩阵乘法:矩阵大小的微小差异,时序差异大 - 代码日志