在C语言程序中,为什么二维数组赋值的顺序会影响效率?
发表于: 2019-08-27 20:19:55 | 已被阅读: 31 | 分类于: C语言
学习C语言最有效的方法就是多做实验,很多初学者深知这一点。小明在学到二维数组时,尝试写了一段给二维数组赋值的代码,他发现一个奇怪的现象:
版本 1
int test1 ()
{
int i,j;
static int x[4000][4000];
for (i = 0; i < 4000; i++) {
for (j = 0; j < 4000; j++) {
x[j][i] = i + j;
}
}
return 0;
}
版本 2
int test2 ()
{
int i,j;
static int x[4000][4000];
for (j = 0; j < 4000; j++) {
for (i = 0; i < 4000; i++) {
x[j][i] = i + j;
}
}
return 0;
}
解析
可能有读者认为这两段C语言代码产生效率上的差异,是因为编译器处理这两段代码时,产生的指令不同。其实不是的,这两段“对称”的C语言代码产生的指令是一致的,请看:
在很多C语言初学者的脑海里,二维数组各个元素在内存中的分布可能是下面这样的:
答案就是 test1() 和 test2() 的缓存命中率不同。计算机 CPU 一般都有更加高速的缓存(称为“缓存线(cache line)”,常是 64 字节),访问这一缓存的速度比访问内存的速度要高的多(读者可以对比想象计算机访问内存的速度比访问硬盘数据的速度快得多)。
小明的C语言代码中赋值的元素是 int 型的(常常是 4 字节),因此 64 字节的缓存线可以容纳 16 个连续的整数,CPU 访问这 16 个数据要比访问内存里的 16 个数据快得多,并且 CPU 在加载缓存线数据的时间内,处理相当多的工作。
CPU 在处理 test2() 中的赋值时,可以获取 16 个连续的整数块,然后赋值给数组,重复
再考虑 test1() 中的赋值,缓存线加载了 16 个整数块,但是接着值修改了其中一个整数,因此它需要重复
小结
本节主要讨论了一个看似“灵异”的C语言二维数组赋值问题,这无关指令差异,更多的是缓存命中差异带来的效率差异。但是读者应该明白,并不是所有的计算机程序都如此,例如 Fortran 语言中,test1() 中的赋值效率要高于 test2() 中的赋值效率,因为它将二维数组在内存中展开时,是按照“列”优先排列的(C语言是按“行”优先排列的)。