这代码居然有差别?CPU友好的代码该这样写
阿里妹导读
本文用实际用例阐述了用心组织的代码也能让性能提升百倍,我们不应该停留在CRUD的漩涡中。下面来看看这个神奇的现象。
一、震惊,这代码居然有差别!
CPU友好的代码与我们平时的那些CRUD操作可能没啥关系。但是用心组织的代码其实也能让性能提升百倍。我们不应该停留在CRUD的漩涡中。今天我给大家带来一个很神奇的现象,文章不长,原理通用,还请大家耐心看完! 我们可以先看下面的矩阵计算。 大家也可以自己思考一下,如果是你来实现一个矩阵的乘法,你会怎么来做。 下图是我给出的A、B、C 三个解题的思路。大家觉得在Jvm里面,下面的代码性能会有区别么?如果有的话,哪一个会快一点?如果没有的话,又为什么?二、为什么会有性能差别?
要想知道这个问题的答案,我们需要知道两个知识点,缺一不可。- 首先,我们需要知道Java二维数组的存储结构是什么样子的。
- 其次,我们需要知道CPU在计算的时候它L1、L2、L3的缓存机制。
2.1.1 知识点一 -- Java二维数组的存储结构
下图便是Java二维数组的一个存储方式示意图,意思是 int[][] array_A = new int[4][3]。2.1.2 知识点二 -- CPU的缓存机制
CPU架构是会演进的,高低端的参数也不一定相同。但我们毕竟不是CPU的制造者,不必每一个CPU都去细扣,我们只需要理解他的原理,在适当的时候做一些抽象方便理解就可以了。 下图是我当前Mac的CPU参数,大家需要注意2个东西,L2缓存、L3缓存。这2个参数就是影响我们今天讨论的性能的主要因素。| 缓存速率表 | ||
| 类型 | 缓存什么 | 延迟(周期数) |
| CPU寄存器 | 4字节或8字节 | 0 |
| L1高速缓存 | 64字节块 | 4 |
| L2高速缓存 | 64字节块 | 10 |
| L3高速缓存 | 64字节块 | 50 |
| 虚拟内存/缓冲区缓存(主存) | 4KB页 | 200 |
2.2.1 友好的遍历方式
假设上面的数据的变量名称是A,成员使用 a 来表述。 我们取数据按照 从左到右,再从上到下 的顺序来进行遍历。2.2.2 不友好的遍历方式
从上到下,再从左到右。三、总结
再来回顾一下我们之前的问题。四、附件
4.1 运行的环境系统参数:
JMH version: 1.36VM version: JDK 11.0.13, Java HotSpot(TM) 64-Bit Server VM, 11.0.13+10-LTS-370型号名称:MacBook Pro型号标识符:MacBookPro15,2处理器名称:四核Intel Core i5处理器速度:2.4 GHz处理器数目:1核总数:4L2缓存(每个核):256 KBL3缓存:6 MB超线程技术:已启用内存:16 GB系统固件版本:1715.60.5.0.0 (iBridge: 19.16.10647.0.0,0)
4.2 整个benchmark的java代码
ArrayTestBenchmark
4.3 多次运行benchmark的结果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];@Setuppublic void setUp() {for (int i = 0; i < N; i++) {for (int j = 0; j < N; j++) {arrays_A[i][j] = i + j;arrays_B[i][j] = i + j;}}}@Benchmarkpublic 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;}@Benchmarkpublic void jik() {final int[][] arrays_C = new int[N][N];for (int j = 0; j < N; j++) {for (int i = 0; i < N; i++) {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;}@Benchmarkpublic void jki() {final int[][] arrays_C = new int[N][N];for (int j = 0; j < N; j++) {for (int k = 0; k < N; k++) {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;}@Benchmarkpublic void kji() {final int[][] arrays_C = new int[N][N];for (int k = 0; k < N; k++) {for (int j = 0; 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;}@Benchmarkpublic void kij() {final int[][] arrays_C = new int[N][N];for (int k = 0; 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;}@Benchmarkpublic void ikj() {final int[][] arrays_C = new int[N][N];for (int i = 0; i < N; i++) {for (int k = 0; k < N; k++) {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的结果收集表格 | |||||||
| 代码 | 500 | 600 | 700 | 800 | 900 | 1000 | 1100 |
| A_ijk | 0.158 | 0.297 | 0.499 | 0.78 | 1.248 | 1.741 | 2.78 |
| A_jik | 0.151 | 0.297 | 0.495 | 0.781 | 1.195 | 1.714 | 2.971 |
| B_jki | 0.338 | 0.696 | 1.446 | 2.971 | 6.363 | 10.368 | 14.191 |
| B_kji | 0.338 | 0.684 | 1.347 | 2.775 | 6.3 | 10.02 | 13.819 |
| C_kij | 0.013 | 0.025 | 0.041 | 0.061 | 0.087 | 0.131 | 0.198 |
| C_ikj | 0.016 | 0.03 | 0.046 | 0.083 | 0.115 | 0.174 | 0.251 |
引用:
- 《深入理解计算机操作系统》
- 《深入理解Java虚拟机》