C语言矩阵操作的缓存性能对比:两种赋值方式孰优?
现有一矩阵,需为每个存在左上邻居的位置(首行、首列元素除外),根据其左上邻居(绿色位置)的0/1值,在新矩阵对应位置(红色位置)设置0/1。若原矩阵位置Matrix[4,3]的左上邻居为1,则新矩阵对应位置设为1,反之设为0。
以下为两种C语言实现代码(C采用行优先存储):
Code 1
for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { if (i > 0 && j > 0) { row = j - 1; col = i - 1; // oldMatrix为原矩阵 newMatrix[j][i] += oldMatrix[row][col]; // newMatrix为目标矩阵 } } }
Code 2
for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { if (i > 0 && j > 0) { row = j - 1; col = i - 1; // oldMatrix为原矩阵 if(oldMatrix[row][col] == 1) { newMatrix[j][i] = 1; } else { newMatrix[j][i] = 0; } } } }
问题
其中Code 1使用newMatrix[j][i] += oldMatrix[row][col],无论值为0或1都会执行读写;Code 2通过条件判断后直接赋值0或1。请问从缓存性能角度,哪种实现更优?原因是什么?
结论:Code 1的缓存性能更优
原因主要有两点:
避免分支预测失效的性能损耗
Code 2里的if(oldMatrix[row][col] == 1)条件判断,如果原矩阵的0、1分布没有明显规律(比如随机分布),CPU的分支预测大概率会出错。每次分支预测失败都会导致CPU流水线清空、重新填充,这会带来不小的性能开销。而Code 1全程没有分支判断,执行流程是线性连续的,CPU流水线可以持续高效运行,不会被分支预测问题打断。内存操作的连续性更契合CPU执行逻辑
两个代码对oldMatrix和newMatrix的访问模式(非连续的列访问)是一致的,缓存命中率在这部分没有差异。但Code 1的+=操作是连续的读-修改-写指令,CPU可以一次性完成内存交互;而Code 2的条件赋值需要先完成分支判断再执行写操作,指令的连续性更差,会额外增加CPU的执行停顿。另外如果newMatrix初始化为全0,Code 1的+=其实等价于直接赋值,但完全规避了分支带来的性能损耗。
内容的提问来源于stack exchange,提问作者hodondo

