有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java阵列访问如何影响性能?

int steps = 256 * 1024 * 1024;
int[] a = new int[2];

// Loop 1
for (int i=0; i<steps; i++) { a[0]++; a[0]++; }

// Loop 2
for (int i=0; i<steps; i++) { a[0]++; a[1]++; }

有人能解释为什么第二个循环比第一个慢20倍(19毫秒对232毫秒)

这就是我计时的方式:

long start_time = System.currentTimeMillis();

// Loop

long end_time = System.currentTimeMillis();
System.out.println(end_time - start_time);

共 (1) 个答案

  1. # 1 楼答案

    摘要

    JIT编译器正在将第一个循环转换为乘法,但没有对第二个循环进行太多优化

    讨论

    两个循环的字节码基本相同(您可以使用javap -c test.class查看)

    在Java中,字节码由JIT编译器转换成x86指令,该编译器能够执行额外的优化

    如果你有hsdis插件,你可以通过java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly ...查看JIT生成的程序集

    我将添加到每个元素的值更改为0xbad,以便更容易发现相关代码,并将循环计数器更改为long

    第一个循环产生:

      mov     r11d,dword ptr [r13+10h]    Load from memory a[0]
      ...
      add     r11d,175ah                  Add 2 * 0xbad to the value
      mov     dword ptr [r13+10h],r11d    Store to memory a[0]
    

    第二个循环产生:

       mov     ebx,dword ptr [rax+10h]    Load from memory a[0]
       add     ebx,0badh                  Add 0xbad
       ...
       mov     dword ptr [rax+10h],ebx    Store to memory
       ...
       mov     ebx,dword ptr [rax+14h]    Load from memory a[1]
       add     ebx,0badh                  Add 0xbad
       ...
       mov     dword ptr [rax+14h],ebx    Store to memory a[1]
    

    因此,您可以看到编译器已经能够将第一个循环优化为更少的指令

    特别是,它发现对同一数组元素的两个加法可以合并为一个值的两倍的加法

    当我将循环计数器改回int时,我注意到编译器在第一个循环中做得更好:

    mov     r10d,dword ptr [r14+10h]
    imul    ecx,r13d,175ah     This line converts lots of adds of 0xbad into a single multiply  
    mov     r11d,r10d
    sub     r11d,ecx
    add     r10d,175ah
    mov     dword ptr [r14+10h],r10d
    

    在本例中,它发现它实际上可以通过使用乘法在一次过程中实现循环的多个迭代!这解释了第一个循环如何比第二个循环快一个数量级