Pie*_*rre 1 java optimization bytecode compilation
我有这两个 Java 和 C++ 代码,它们应该做同样的事情。
我的直觉是, 和 的目标代码的大小(以及内容)将R1相同R2。C++ 就是这种情况(如果编译时没有 4 个字节的差异-O1)。Java 字节码有更大的差异(R2更长),这让我感到惊讶。
也许我没有看到正确的东西,我的问题可能不相关,但是Java字节码与源代码如此“接近”是正常的吗?这是否意味着它总是更“高效”/“优化” “将所有内容写在一行中而不是使用局部变量?
C++
int A(int a) { return 0; }
int B(int b) { return 0; }
int C(int c) { return 0; }
int D(int d) { return 0; }
int R1() {
return A(B(C(3)+D(3)));
}
int R2() {
int d = D(3);
int c = C(3);
int b = B(c + d);
return A(b);
}
// Then R1() and R2() are called in the main()
Run Code Online (Sandbox Code Playgroud)
爪哇
class MyClass {
static int A(int a) { return 0; }
static int B(int b) { return 0; }
static int C(int c) { return 0; }
static int D(int d) { return 0; }
static int R1() {
return A(B(C(3)+D(3)));
}
static int R2() {
int d = D(3);
int c = C(3);
int b = B(c + d);
return A(b);
}
// Then R1 and R2 are called in the Main()
}
Run Code Online (Sandbox Code Playgroud)
当我编译它们(g++ -O1版本 9.4 和javac版本 11.0.17)并反汇编R1和R2时,我得到:
C++ ( g++ -O1 prog.cpp)
R1:
1251: f3 0f 1e fa endbr64
1255: 53 push %rbx
1256: bf 03 00 00 00 mov $0x3,%edi
125b: e8 9d ff ff ff callq 11fd <_Z1Ci>
1260: 89 c3 mov %eax,%ebx
1262: bf 03 00 00 00 mov $0x3,%edi
1267: e8 bb ff ff ff callq 1227 <_Z1Di>
126c: 8d 3c 03 lea (%rbx,%rax,1),%edi
126f: e8 5f ff ff ff callq 11d3 <_Z1Bi>
1274: 89 c7 mov %eax,%edi
1276: e8 2e ff ff ff callq 11a9 <_Z1Ai>
127b: 5b pop %rbx
127c: c3 retq
R2:
<exact same as R1>
Run Code Online (Sandbox Code Playgroud)
爪哇 ( javap -c MyClass)
javap -c Appel
static int R1();
Code:
0: iconst_3
1: invokestatic #8 // Method C:(I)I
4: iconst_3
5: invokestatic #9 // Method D:(I)I
8: iadd
9: invokestatic #10 // Method B:(I)I
12: invokestatic #11 // Method A:(I)I
15: ireturn
static int R2();
Code:
0: iconst_3
1: invokestatic #9 // Method D:(I)I
4: istore_0
5: iconst_3
6: invokestatic #8 // Method C:(I)I
9: istore_1
10: iload_1
11: iload_0
12: iadd
13: invokestatic #10 // Method B:(I)I
16: istore_2
17: iload_2
18: invokestatic #11 // Method A:(I)I
21: ireturn
Run Code Online (Sandbox Code Playgroud)
不。您所缺少的细节正是优化发生的地方。
在 C 语言中,读取源代码的应用程序(gcc例如)是执行大部分优化的应用程序(尽管越来越多的优化是由 CPU 本身在其管道和微码翻译引擎中完成的 - 并不是说有一个作为一名程序员,你可以做很多事情来影响这一点)。因此,该应用程序 ( gcc) 具有-o(优化级别)选项,并且该应用程序可能会消耗大量 CPU 周期来分析您的代码。
在 java 领域,这不是它的工作原理。javac已步入正轨:规范准确地规定了它应该生成的内容,几乎精确到字节——与 C 语言相反,其中规范用“mays”和“coulds”堆叠到了鳃上——编译器有很大的回旋余地,特别是包括基本“字”的位宽,而 java 将所有这些锁定在规范中,无论您最终运行的 CPU 架构的位宽如何。
优化是由java.exe- 运行时完成的。一般来说,它的方法比 C 更高效,因为与 C 不同,运行时的好处是能够检查“实时行为”,而不是 C 编译器可以做的事情(这就是为什么 C 编译器倾向于有很多“提示”系统,您可以在其中通知编译器您怀疑运行时行为可能是什么)。
所有现代 JVM 的工作方式都是低效运行代码(javac 生成的看似低效的代码,您可以通过 注意到javap -v),而且实际上运行效率甚至比这更低,因为 JVM 会添加一堆钩子来添加簿记。例如,JVM 将跟踪调用方法的频率、运行时间,以及例如,对于每个if,计算每个分支的执行频率。所有这些都使它运行得更慢。
JVM 之所以这样做,是因为对于 99%(字面意思)的所有程序来说,大约 99%(再次强调,字面意思,并不夸张)的 CPU/内存“花费”在不到 1% 的代码上,但是技巧就是,试图预测那是哪 1%。通过所有这些簿记,java 将知道,然后分析那 1% 的字节码,并使用迄今为止由于所有这些簿记而观察到的运行时行为,以得出经过微调的机器代码。这意味着 java 可以编写分支预测的代码(确保机器代码不必跳转到最常采用的分支路径),除非它不是预测:java.exe 知道哪一个是“最常采用的路径”。与gcc必须猜测的情况相比,程序员可以选择通过源文件中的分支提示来协助。
这只是数千个java.exe可以应用机器代码优化的地方之一。
这仍然意味着大约 99% 的代码运行效率非常低。但是,考虑到这仅占用不到 1% 的 CPU/内存,所以这并不重要。
由于各种因素,Java 比 C 慢,但“优化指令”不是其中之一:
当然,对于简单的命令行一次性工具(如ls或 )来说,java 既不流行,也不是一个好主意/bin/true。例如,在编写 Web 请求响应程序时,Java 的性能非常出色。它们会运行很长时间,而热点进程确实有帮助。