三、总结
再来回顾一下我们之前的问题。
C代码块 是横向优先的访问方式。
A代码块 里面对 arrays_A 的方式是横向顺序访问的,但是在处理 arrays_B 的时候就是纵向遍历的。
B代码块 所有的访问都是纵向的(不友好的遍历方式)。因为发挥不出CPU缓存的效果,所以性能最差。
Java的二维数组在内存里面是行连续的,但是行与行之间不一定连续。CPU在缓存大小有限的情况下,不可能把所有的数据都缓存下来。再加上每一层级访问速度的硬件限制,就导致了上面的性能结果。
相信大家也和我一样,知道原理之后,也不是那么迷惑了。
在实际的业务环境中,我们不一定能遇到这种纯计算的场景。但是我们还是应该尽量顺序访问数据,不管是什么样的数据。投其所好,方能够优化代码性能。
其次,我们在访问数据的时候,还是需要了解各种语言背后实际的存储结构和CPU的缓存原理,本次是讲述的是Java,但是这个思想其他语言其实也是受用的。
四、附件
4.1 运行的环境
系统参数:
JMH version: 1.36
VM version: JDK 11.0.13, Java HotSpot(TM) 64-Bit Server VM, 11.0.13+10-LTS-370
JMH version: 1.36 VM version: JDK 11.0.13,Java HotSpot(TM) 64-Bit Server VM,11.0.13+10- 型号名称:MacBook Pro 型号标识符:MacBookPro15,2 处理器名称:四核Intel Core i5 处理器速度: 2.4 GHz 处理器数目: 1 核总数: 4 L2缓存 (每个核) : 256 KB L3缓存:6 MB 超线程技术:已启用 内存:16 GB 系统固件版本: 1715.60.5.0.0 (iBridge: 19.16.10647.0.0,0)
4.2 整个benchmark的java代码
ArrayTestBenchmark
import org.openjdk.jmh.annotations.*; /** *矩阵C=AB 的计算 * * @author wzj* * @date 2023/02/09 */ @BenchmarkMode(Mode.AverageTime) @State(value = Scope.Benchmark) // 预热3次 @Warmup(iterations = 3, time = 1) // 循环 10 次 @Measurement(iterations = 10, time = 1) public class ArrayTestBenchmark { private final int N = 1000; private final int[][] arrays_A = new int[N][N]; private final int[][] arrays B = new int[N][N]; @Setup public void setUp() { for (int i=; i< N; i++) { for (int j =0; j< N; j++) { arrays_A[i][j] = i + j; arrays B[i][jl = i + j; } } } @Benchmark public void ijk() { final int[][] arrays_C = new int[N] [N]; for (int i=0; i<N; i++) { for (int j=0; j < N; j++) { int sum = 0; for (int k=0; k < N; k++){ sum += arrays A[i][k] * arrays B[k][j]; } arrays_C[i][j] += sum; } } assert arrays_C.length > 0; } @Benchmark public void jik(){ final int[][] arrays_C = new int[N][N]; for (int j =@; j< N; j++) { for (int i=@; i< N; i++) { int sum = 0; for (int k =; k< N; k++) { sum += arrays A[i][k] * arrays B[k][j]; } arrays_C[i][j] += sum; } } assert arrays_C.length > 0; } @Benchmark public void jki() { final int[][] arrays_C = new int[N][N]; for (int j =; j < N; j++){ for (int k =0; k< N; k++) { int r_B = arrays_B[k][j]; for (int i=@; i< N; i++) { arrays_C[i][j] += arrays_A[i][k] * r_B; } } } assert arrays_C.length > 0; } @Benchmark public void kji() { final int[][] arrays_C = new int[N] [N]; for (int k =o; k < N; k++) { for (int j =@; j < N; j++) { int r_B = arrays_B[k][j]; for (int i=0; i< N; i++) { arrays_C[i][j] += arrays A[i][k] * r_B; } } } assert arrays C.length > 0; } @Benchmark public void kij() { final int[][] arrays C = new int[N][N]; for (int k =; k < N; k++) { for (int i=0; i < N; i++) { int r A = arrays A[k][i]; for (int j=0; j <N; j++) { arrays_C[i][j] += r_A * arrays_B[k][j]; } } } assert arrays C.length > 0; } @Benchmark public void ikj() { final int[][] arrays_C = new int[N][N]; for (int i=0; i< N; i++) { for (int k =o; k < N; k++) { int r_A = arrays A[k][i]; for (int j =@; j < N; j++) { arrays_C[i][j] += r_A *arrays_B[k][j]; } } } assert arrays_C.length > 0; } }
4.3 多次运行benchmark的结果
引用:
- 《深入理解计算机操作系统》
- 《深入理解Java虚拟机》