求解答一个 Java 运行速度的问题

2020-03-26 15:32:28 +08:00
 goodboy95

下面的 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));
}
2845 次点击
所在节点    Java
17 条回复
ak47007
2020-03-26 15:36:33 +08:00
我有两个疑问?
ak47007
2020-03-26 15:37:28 +08:00
1. 为什么不用 System.currentTimeMillis()
2. 为什么不将 int baseNum=6; 写在外面?
drquack
2020-03-26 15:53:45 +08:00
我对 Java 不熟,但是这个应该和循环优化有关,加注释之前 n 和 baseNum 的作用域都在 for 循环以内,所以可能就被循环就被优化掉了。

如果你改一下加注释后的代码,把 `int n = 0` 放到循环里面,速度会比加注释之前更快。
goodboy95
2020-03-26 15:55:00 +08:00
@ak47007 我 java 也是刚学几天,currentTimeMillis 这个之前真不知道……
之前看网上有人讨论 java 乘法和位运算的速度的时候,就随手写了这段代码。最开始的时候我写的 n = baseNum * 1024,至于 int baseNum = 6;那一行本来我是想之后改成 int baseNum = i 的,先写个常数试试,结果试完之后发现了现在这个奇怪的情况。
不过 int baseNum = 6;写到外面之后,for 循环里面不管有没有注释都是 600ms 了,难道是因为现在这个代码去掉注释就会把 n 的数值做成常量?
drquack
2020-03-26 15:56:28 +08:00
(想起了写编译器的时候各种循环优化的骚操作
goodboy95
2020-03-26 16:26:57 +08:00
话说回来,把注释掉的代码加进去,去掉 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 的优化也太厉害了点吧……
luckyrayyy
2020-03-26 16:28:21 +08:00
很有意思的一个问题,我对代码进行了删减,基本可以确定和那两行注释没关系,和 res |= n 这个具体操作换成+=之类的运算也是有很大差距。基本可以确定在循环外创建变量并且赋值导致的。我再研究下具体怎么回事
luckyrayyy
2020-03-26 16:43:30 +08:00
找打了 R 大知乎的一个回答,试了试果然如此
https://www.zhihu.com/question/58735131
OysterQAQ
2020-03-26 17:11:07 +08:00
问题在于 n 的赋值吧
goodboy95
2020-03-26 17:17:20 +08:00
@OysterQAQ 把 n 写成 final 之后,速度确实快了(虽然字节码上看不出什么区别……)
上面也有人提到 JIT 在里面搞事,我也在看。
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;
OysterQAQ
2020-03-26 20:19:02 +08:00
OysterQAQ
2020-03-26 20:19:53 +08:00
具体可以看楼上 差别就是循环内赋值的话多两条指令 多两条 80ms 少两条 600ms 感觉和 cpu 缓存有关了
secondwtq
2020-03-27 04:51:50 +08:00
拿 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 换成一次是一样的
当然全都优化之后是个常数,就量不出时间了
warcraft1236
2020-03-27 11:07:10 +08:00
想知道 |= 这个是啥意思?
goodboy95
2020-03-27 15:46:40 +08:00
@warcraft1236 按位取或,写这个的原因纯粹是让循环对外干点事情,防止 java 把 for 循环优化掉。
yjxjn
2020-03-30 18:34:07 +08:00
兄弟,我试了一下,macOS 10.14 Eclipse JDK1.8 注释不注释 都是 500ms 左右

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/656407

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX