将 int 乘以 30、31、32 - 这些真的被编译器优化了吗? (有效java这么说的)
Multiply an int by 30, 31, 32 - are these really optimized by the compiler? (effective java says so)
我一直在阅读 Effective Java, 3/E.
在阅读有关哈希码的部分时,(第 51 页)我注意到书上说
A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance on some architectures: 31 * i == (i << 5) - i
. Modern VMs do this sort of optimization automatically.
我认为这是有道理的。我想知道当这种优化发生时,代码会变得多快。所以我写了一个简短的代码来查看此类优化的影响。
但是,似乎没有明显的差异。所以我写了更简单的代码,来检查是否发生了这种优化。
下面是我的示例代码。
fun main() {
val num = Random.nextInt()
val a = num * 30
val b = num * 31
val c = num * 32
println("$a, $b, $c")
}
这是编译后的机器码,来自 IntelliJ 的 Kotlin 字节码功能。
L1
LINENUMBER 5 L1
ILOAD 0
BIPUSH 30
IMUL
ISTORE 1
L2
LINENUMBER 6 L2
ILOAD 0
BIPUSH 31
IMUL
ISTORE 2
L3
LINENUMBER 7 L3
ILOAD 0
BIPUSH 32
IMUL
ISTORE 3
显然,没有区别。我们推送每个数字,然后调用 IMUL
。我认为优化可能发生在 Java 字节码被编译成实际机器码时,但我从未检查过那方面,所以我不知道如何证实我的理论。我搜索了一下,我要查找的关键字似乎是 JIT 编译器,它似乎将 .class 转换为 cpu 特定的机器代码。
我想,也许我可以尝试通过 JIT 编译器将此代码转换为 cpu 特定的机器代码,但这意味着我在一个特定的 CPU 上检查了这个理论,而不是所有 CPU秒。我想看看它是否“大体上是正确的”,但这会花费太多时间。
那么,有什么方法可以确认上面的代码实际上(通常)被编译器优化了吗?
如果我以后有类似的问题,应该去哪里找?我的意思是,当我对 java 行为感到好奇时,我会去 oracle 并检查 JVM 参考或 java se 参考。但是编译器的行为呢?我应该从哪里开始?
这是一个很长的问题。感谢您抽出宝贵时间阅读本题。
(补充说明)
我在 https://godbolt.org/ 上检查了 C 和 python,并确认对于 C,它实际上是优化的。
int test(int num) {
int n = rand();
int a= n*30;
int b= n*31;
int c= n*32;
return a * b * c;
}
test:
push rbp
mov rbp, rsp
sub rsp, 32
mov DWORD PTR [rbp-20], edi
call rand
mov DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]
imul eax, eax, 30
mov DWORD PTR [rbp-8], eax
mov edx, DWORD PTR [rbp-4]
mov eax, edx
sal eax, 5
sub eax, edx
mov DWORD PTR [rbp-12], eax
mov eax, DWORD PTR [rbp-4]
sal eax, 5
mov DWORD PTR [rbp-16], eax
mov eax, DWORD PTR [rbp-8]
imul eax, DWORD PTR [rbp-12]
imul eax, DWORD PTR [rbp-16]
leave
ret
但是,python,不是。
num = randint()
a = num * 30
b = num * 31
c = num * 32
5 18 LOAD_NAME 2 (num)
20 LOAD_CONST 2 (30)
22 BINARY_MULTIPLY
24 STORE_NAME 3 (a)
6 26 LOAD_NAME 2 (num)
28 LOAD_CONST 3 (31)
30 BINARY_MULTIPLY
32 STORE_NAME 4 (b)
7 34 LOAD_NAME 2 (num)
36 LOAD_CONST 4 (32)
38 BINARY_MULTIPLY
40 STORE_NAME 5 (c)
42 LOAD_CONST 5 (None)
44 RETURN_VALUE
类似 C are compiled Ahead-Of-Time, which means all optimizations are done in compile time since they are compiled to assembly code 并由本地机器解释的语言。
Kotlin, Scala, Java, etc. JVM languages runs on the Java Virtual Machine. Implementations of the JVM does runtime optimizations. This is called Just-In-Time Compilation. An example of a JIT compiler is HotSpot, like its name it finds “hot spots” of JVM code and compile and optimize it to assembly. An alternative JIT to HotSpot is OpenJ9.
像 Python, I believe, are interpreted at runtime 这样的语言,这意味着根本不涉及优化。但是 python 的 AOT 编译器实际上可能会做一些优化,但我真的不知道这些编译器的实现细节。
我一直在阅读 Effective Java, 3/E.
在阅读有关哈希码的部分时,(第 51 页)我注意到书上说
A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance on some architectures:
31 * i == (i << 5) - i
. Modern VMs do this sort of optimization automatically.
我认为这是有道理的。我想知道当这种优化发生时,代码会变得多快。所以我写了一个简短的代码来查看此类优化的影响。
但是,似乎没有明显的差异。所以我写了更简单的代码,来检查是否发生了这种优化。
下面是我的示例代码。
fun main() {
val num = Random.nextInt()
val a = num * 30
val b = num * 31
val c = num * 32
println("$a, $b, $c")
}
这是编译后的机器码,来自 IntelliJ 的 Kotlin 字节码功能。
L1
LINENUMBER 5 L1
ILOAD 0
BIPUSH 30
IMUL
ISTORE 1
L2
LINENUMBER 6 L2
ILOAD 0
BIPUSH 31
IMUL
ISTORE 2
L3
LINENUMBER 7 L3
ILOAD 0
BIPUSH 32
IMUL
ISTORE 3
显然,没有区别。我们推送每个数字,然后调用 IMUL
。我认为优化可能发生在 Java 字节码被编译成实际机器码时,但我从未检查过那方面,所以我不知道如何证实我的理论。我搜索了一下,我要查找的关键字似乎是 JIT 编译器,它似乎将 .class 转换为 cpu 特定的机器代码。
我想,也许我可以尝试通过 JIT 编译器将此代码转换为 cpu 特定的机器代码,但这意味着我在一个特定的 CPU 上检查了这个理论,而不是所有 CPU秒。我想看看它是否“大体上是正确的”,但这会花费太多时间。
那么,有什么方法可以确认上面的代码实际上(通常)被编译器优化了吗? 如果我以后有类似的问题,应该去哪里找?我的意思是,当我对 java 行为感到好奇时,我会去 oracle 并检查 JVM 参考或 java se 参考。但是编译器的行为呢?我应该从哪里开始?
这是一个很长的问题。感谢您抽出宝贵时间阅读本题。
(补充说明)
我在 https://godbolt.org/ 上检查了 C 和 python,并确认对于 C,它实际上是优化的。
int test(int num) {
int n = rand();
int a= n*30;
int b= n*31;
int c= n*32;
return a * b * c;
}
test:
push rbp
mov rbp, rsp
sub rsp, 32
mov DWORD PTR [rbp-20], edi
call rand
mov DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]
imul eax, eax, 30
mov DWORD PTR [rbp-8], eax
mov edx, DWORD PTR [rbp-4]
mov eax, edx
sal eax, 5
sub eax, edx
mov DWORD PTR [rbp-12], eax
mov eax, DWORD PTR [rbp-4]
sal eax, 5
mov DWORD PTR [rbp-16], eax
mov eax, DWORD PTR [rbp-8]
imul eax, DWORD PTR [rbp-12]
imul eax, DWORD PTR [rbp-16]
leave
ret
但是,python,不是。
num = randint()
a = num * 30
b = num * 31
c = num * 32
5 18 LOAD_NAME 2 (num)
20 LOAD_CONST 2 (30)
22 BINARY_MULTIPLY
24 STORE_NAME 3 (a)
6 26 LOAD_NAME 2 (num)
28 LOAD_CONST 3 (31)
30 BINARY_MULTIPLY
32 STORE_NAME 4 (b)
7 34 LOAD_NAME 2 (num)
36 LOAD_CONST 4 (32)
38 BINARY_MULTIPLY
40 STORE_NAME 5 (c)
42 LOAD_CONST 5 (None)
44 RETURN_VALUE
类似 C are compiled Ahead-Of-Time, which means all optimizations are done in compile time since they are compiled to assembly code 并由本地机器解释的语言。
Kotlin, Scala, Java, etc. JVM languages runs on the Java Virtual Machine. Implementations of the JVM does runtime optimizations. This is called Just-In-Time Compilation. An example of a JIT compiler is HotSpot, like its name it finds “hot spots” of JVM code and compile and optimize it to assembly. An alternative JIT to HotSpot is OpenJ9.
像 Python, I believe, are interpreted at runtime 这样的语言,这意味着根本不涉及优化。但是 python 的 AOT 编译器实际上可能会做一些优化,但我真的不知道这些编译器的实现细节。