有一段时间在x86和arm服务器下面做开发,需要平台之间的移植,然后经常发现同一段代码在不同平台下面的表现不一样,有一大部分原因是不同平台对cache处理方法不一样。
大部分参考资料上说,cache有时间和空间的局部性,空间的局部性是指未来要用到的信息会和当前的信息靠在一起,即a[i] 会和a[i+1]在同一条cache line上, 顺序存储。时间的局部性是指未来访问的信息很有可能是现在访问的信息, 累加或者循环结构。
就空间局部性来说,如果访问某个变量时,它在cache 里面,称cache hit, 否则cache miss. cache 一般分l1, l2, l3 三层,每层发生cache miss的时候, 大概要花几个cycles 到几百个cycles不等。
下面的例子就是来探讨x86/ARM cache.
例子A: 矩阵累加
sum_matrix: 先row 在column
sum_matrix1: 先column在row
Intel(R) Xeon(R) CPU E5-2680:
icc -O3 test_sum.c -o test_sum
差别很小
In Arm Cortex A72:
aarch64-linux-gnu-gcc -O3 test_sum_trail1.c -o test_sum
跑出来的结果:
结果非常明显,顺序读的效果比跳读要快一倍。
例子二: 矩阵乘法 - 分块
Intel(R) Xeon(R) CPU E5-2680:
icc -O3 test_block_matrix.c -o test_block_matrix
没有效果。修改一下程序,把block size作为一个输入
完全没效果^_^_^_^_^_^
In Arm Cortex A72:
aarch64-linux-gnu-gcc -O3 test_block_matrix.c -o test_block_matrix
太夸张了,5 倍的性能提升。
Cpu l1d 32 Kbytes, 把分块矩阵的大小设置为64 * 64, 那么所有的矩阵的大小 64 * 64 * 3(A, B, C)* 4 (sizeof(int)) = 48 K, 我们看看效果,
把分块矩阵的大小设置为8*8似乎是最合适的,并非分块矩阵占满整个l1 cache才算。
总结:
随着自己对cpu的认知越来越深,我也尝试着用一些自己的理解去解释上面的现象, 比如,我通过查手册,看到,arm cpu一次性可以load 6 个cache line, 那么我就想x86是不是多于这个呢,结果是4个(不太确定):
也许和cpu的硬件预取功能有关,x86的预取效果好,正如x86的分支预测做的比arm的好。。。
或许x86的并行性做的比arm好。。。。。
Anyway, 对于x86的开发者,可以更加关注应用层,而非底层cpu的运行逻辑了,而对于arm开发者在开发软件时,一定要把cache的局部性放在心里。。。