下面的 java 程序,在我的电脑上运行时间 600ms 左右,但把 for 循环里面注释掉的两行加回来的话,时间反而会降到 50-60ms,有人明白这是什么情况吗? java 版本是 JDK11,windows ( win10 )和 linux ( ubuntu 18.04 )均有这种情况。
顺便问一下,spring 是自身做了缓存吗,我把这段代码和一个依赖外部参数的 sql 查询代码放在 spring boot 项目的一个接口里面,每次调用都修改参数的话,只有前两次会耗比较长时间,后面基本就不耗时间了……
public static void main(String[] args) {
long t1 = new Date().getTime();
int res = 0;
int n = 0;
for (int i = 0; i < 2000000000; i++) {
// int baseNum = 6;
// n = baseNum * 1346;
res |= n;
}
long t2 = new Date().getTime();
System.out.println(String.format("time: %d, res: %d", t2 - t1, res));
}
1
ak47007 2020-03-26 15:36:33 +08:00
我有两个疑问?
|
2
ak47007 2020-03-26 15:37:28 +08:00
1. 为什么不用 System.currentTimeMillis()
2. 为什么不将 int baseNum=6; 写在外面? |
3
drquack 2020-03-26 15:53:45 +08:00 1
我对 Java 不熟,但是这个应该和循环优化有关,加注释之前 n 和 baseNum 的作用域都在 for 循环以内,所以可能就被循环就被优化掉了。
如果你改一下加注释后的代码,把 `int n = 0` 放到循环里面,速度会比加注释之前更快。 |
4
goodboy95 OP @ak47007 我 java 也是刚学几天,currentTimeMillis 这个之前真不知道……
之前看网上有人讨论 java 乘法和位运算的速度的时候,就随手写了这段代码。最开始的时候我写的 n = baseNum * 1024,至于 int baseNum = 6;那一行本来我是想之后改成 int baseNum = i 的,先写个常数试试,结果试完之后发现了现在这个奇怪的情况。 不过 int baseNum = 6;写到外面之后,for 循环里面不管有没有注释都是 600ms 了,难道是因为现在这个代码去掉注释就会把 n 的数值做成常量? |
5
drquack 2020-03-26 15:56:28 +08:00
(想起了写编译器的时候各种循环优化的骚操作
|
6
goodboy95 OP 话说回来,把注释掉的代码加进去,去掉 Date 相关代码和 println 之后,用 javap -c 查看的时候,结果是这样的:
``` public static void main(java.lang.String[]); Code: 0: iconst_0 1: istore_1 2: iconst_0 3: istore_2 4: iconst_0 5: istore_3 6: iload_3 7: ldc #2 // int 2000000000 9: if_icmpge 33 12: bipush 6 14: istore 4 16: iload 4 18: sipush 1346 21: imul 22: istore_2 23: iload_1 24: iload_2 25: ior 26: istore_1 27: iinc 3, 1 30: goto 6 33: return ``` 里面把 for 循环完完整整的写了一遍……JVM 的优化也太厉害了点吧…… |
7
luckyrayyy 2020-03-26 16:28:21 +08:00
很有意思的一个问题,我对代码进行了删减,基本可以确定和那两行注释没关系,和 res |= n 这个具体操作换成+=之类的运算也是有很大差距。基本可以确定在循环外创建变量并且赋值导致的。我再研究下具体怎么回事
|
8
luckyrayyy 2020-03-26 16:43:30 +08:00 2
找打了 R 大知乎的一个回答,试了试果然如此
https://www.zhihu.com/question/58735131 |
9
OysterQAQ 2020-03-26 17:11:07 +08:00
问题在于 n 的赋值吧
|
10
goodboy95 OP @OysterQAQ 把 n 写成 final 之后,速度确实快了(虽然字节码上看不出什么区别……)
上面也有人提到 JIT 在里面搞事,我也在看。 |
11
OysterQAQ 2020-03-26 17:31:46 +08:00
long t1 =System.currentTimeMillis();
int res = 0; int n = 0; for (int i = 0; i < 2000000000; i++) { n = 6 * 1346; res |= n; } long t2 =System.currentTimeMillis(); System.out.println(String.format("time: %d, res: %d", t2 - t1, res)); 问题只在于 n = 6 * 1346; |
12
OysterQAQ 2020-03-26 20:19:02 +08:00 1
|
13
OysterQAQ 2020-03-26 20:19:53 +08:00
具体可以看楼上 差别就是循环内赋值的话多两条指令 多两条 80ms 少两条 600ms 感觉和 cpu 缓存有关了
|
14
secondwtq 2020-03-27 04:51:50 +08:00 1
拿 11 楼的例子粗略看了一下
36 ms 的汇编: 0x00007f6130116540: mov r9d,r13d ;*goto ; - Benchmark::doTest@30 (line 8) 0x00007f6130116543: or ebx,0x7b ;*ior ; - Benchmark::doTest@25 (line 10) 0x00007f6130116546: mov r13d,r9d 0x00007f6130116549: add r13d,0x10 ;*iinc ; - Benchmark::doTest@27 (line 8) 0x00007f613011654d: cmp r13d,0x773593f1 0x00007f6130116554: jl 0x00007f6130116540 ;*if_icmpge ; - Benchmark::doTest@17 (line 8) 531 ms 的汇编: 0x00007f5b8d070650: mov r9d,r13d ;*goto ; - Benchmark::doTest@27 (line 8) 0x00007f5b8d070653: or r14d,ebx 0x00007f5b8d070656: or r14d,ebx 0x00007f5b8d070659: or r14d,ebx 0x00007f5b8d07065c: or r14d,ebx 0x00007f5b8d07065f: or r14d,ebx 0x00007f5b8d070662: or r14d,ebx 0x00007f5b8d070665: or r14d,ebx 0x00007f5b8d070668: or r14d,ebx 0x00007f5b8d07066b: or r14d,ebx 0x00007f5b8d07066e: or r14d,ebx 0x00007f5b8d070671: or r14d,ebx 0x00007f5b8d070674: or r14d,ebx 0x00007f5b8d070677: or r14d,ebx 0x00007f5b8d07067a: or r14d,ebx 0x00007f5b8d07067d: or r14d,ebx 0x00007f5b8d070680: or r14d,ebx ;*ior ; - Benchmark::doTest@22 (line 10) 0x00007f5b8d070683: mov r13d,r9d 0x00007f5b8d070686: add r13d,0x10 ;*iinc ; - Benchmark::doTest@24 (line 8) 0x00007f5b8d07068a: cmp r13d,0x773593f1 0x00007f5b8d070691: jl 0x00007f5b8d070650 ;*if_icmpge 可见两边都做了 16 次的 unroll,两边的时间基本也是差 16 倍左右 但是大概这里编译器并不知道 outer scope 的变量具体是什么值,所以如果不在循环内赋值,就会强行做 16 次 or 感觉这个优化还没开全 ... 这 16 次 or 换成一次是一样的 当然全都优化之后是个常数,就量不出时间了 |
15
warcraft1236 2020-03-27 11:07:10 +08:00
想知道 |= 这个是啥意思?
|
16
goodboy95 OP @warcraft1236 按位取或,写这个的原因纯粹是让循环对外干点事情,防止 java 把 for 循环优化掉。
|
17
yjxjn 2020-03-30 18:34:07 +08:00
兄弟,我试了一下,macOS 10.14 Eclipse JDK1.8 注释不注释 都是 500ms 左右
|