将 int 乘以 30、31、32 - 这些真的被编译器优化了吗?(有效的java是这么说的)

Che*_*eon 5 java optimization compiler-optimization kotlin

我一直在阅读 Effective Java,3/E。

在阅读有关哈希码的部分时,(第 51 页)我注意到这本书说

31一个很好的特性是乘法可以通过移位,并有更好的表现在某些架构减法来代替:31 * i == (i << 5) - i。现代虚拟机会自动进行这种优化。

我认为这是有道理的。我想知道当这种优化发生时,代码会变得多快。所以我写了一个简短的代码来看看这种优化的影响。

但是,似乎没有明显的差异。所以我写了更简单的代码,以检查是否发生了这种优化。

下面是我的示例代码。

fun main() {
    val num = Random.nextInt()
    val a = num * 30
    val b = num * 31
    val c = num * 32

    println("$a, $b, $c")
}
Run Code Online (Sandbox Code Playgroud)

这是编译后的机器代码,来自 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
Run Code Online (Sandbox Code Playgroud)

显然,没有区别。我们推送每个号码,然后调用IMUL。我想也许优化发生在Java字节码被编译成实际机器码时,但我从来没有检查过那一面,所以我不知道如何证实我的理论。我搜索了一下,似乎我要找的关键字是 JIT 编译器,它似乎将 .class 转换为特定于 cpu 的机器代码。

我想,也许我可以尝试通过 JIT 编译器将此代码转换为特定于 cpu 的机器代码,但这意味着我在一个特定的 CPU 上检查了这个理论,而不是所有的 CPU。我想看看它是否“大致正确”,但这会花费太多时间。

那么,有没有办法确认上面的代码实际上(通常)被编译器优化了?如果我以后有类似的问题,我应该去哪里寻找?我的意思是,当我对 java 行为感到好奇时,我会去 oracle 并检查 JVM 引用或 java se 引用。但是编译器的行为呢?我应该从哪里开始?

这是一个很长的问题。感谢您花宝贵的时间阅读这个问题。

(只是一个补充说明)

我在https://godbolt.org/上检查了 C 和 python ,并确认对于 C,它实际上是优化的。

   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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

但是,python 不是。

int test(int num) {
    int n = rand();
    int a= n*30;
    int b= n*31;
    int c= n*32;
    return a * b * c;
}
Run Code Online (Sandbox Code Playgroud)
  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
Run Code Online (Sandbox Code Playgroud)

Dea*_*eef 7

C这样的语言提前编译的,这意味着所有优化都是在编译时完成的,因为它们被编译为汇编代码并由本地机器解释。

KotlinScalaJavaJVM 语言Java 虚拟机上运行。JVM 的实现进行运行时优化。这称为即时编译。JIT 编译器的一个例子是HotSpot,就像它的名字一样,它可以找到 JVM 代码的“热点”并将其编译和优化为汇编。HotSpot 的替代 JIT 是OpenJ9

我相信像Python这样的语言是在运行时解释的,这意味着根本不涉及优化。但是python的AOT编译器实际上可能会做一些优化,但我并不真正了解这些编译器的实现细节。