阵列访问如何影响性能?

时间:2017-07-21 19:19:55

标签: java algorithm performance

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毫秒vs 232毫秒)?

这就是我计时的方式:

long start_time = System.currentTimeMillis();

// Loop

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

1 个答案:

答案 0 :(得分:9)

摘要

JIT编译器正在将第一个循环转换为乘法,但不是非常优化第二个循环。

讨论

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

在Java中,字节码由JIT编译器转换为x86指令,JIT编译器能够执行其他优化。

如果您有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

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