Sunday, December 9, 2012

Mastering Java: Boxing vs MutableInteger vs int[], Part 2

In previous post we've discussed the speed of access to int[0] vs wrapped field (using MutableInteger). And we've realised that the access to object's field is much faster then to a first element of array. We've found this interesting and decided to investigate why does this happen.
So let's create a simple test:
    public static void incArray(int[] array) {
        array[0] = +1;

    public static void incObject(MutableInteger integer) {
        integer.value = +1;
and look at a byte-code "javap -c Calculate"
public static void incArray(int[]);
   0: aload_0
   1: iconst_0
   2: iconst_1
   3: iastore
   4: return

public static void incObject(Calculate$MutableInteger);
   0: aload_0
   1: iconst_1
   2: putfield #2; //Field Calculate$MutableInteger.value:I
   5: return
In fact this gives us nothing cause accessing the field and array uses different commands. So let's take a look at assembler code generated by HotSpot for this method:
        java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly Calculate
#in order to get assembler output you need open-jdk + hsdis and follow the instructions by Alex


        [Verified Entry Point]
          # {method} 'incArray' '([I)V' in 'Calculate'
          # parm0:    ecx       = '[I'
          #           [sp+0x20]  (sp of caller)
          0xb58d1b40: mov    %eax,-0x3000(%esp)
          0xb58d1b47: push   %ebp
          0xb58d1b48: sub    $0x18,%esp         ;*aload_0
                                                ; - Calculate::incArray@0 (line 15)
          0xb58d1b4b: cmpl   $0x0,0x8(%ecx)     ; implicit exception: dispatches to 0xb58d1b6a
          0xb58d1b52: jbe    0xb58d1b74
          0xb58d1b58: movl   $0x1,0xc(%ecx)     ;*iastore
                                                ; - Calculate::incArray@3 (line 15)
          0xb58d1b5f: add    $0x18,%esp
          0xb58d1b62: pop    %ebp
          0xb58d1b63: test   %eax,0xb78c5100    ;   {poll_return}
          0xb58d1b69: ret
          0xb58d1b6a: call   0xb58cf590         ; OopMap{ecx=Oop off=47}
                                                ; - Calculate::incArray@3 (line 15)
                                                ;   {runtime_call}
          0xb58d1b6f: call   0xb58cf590         ; OopMap{ecx=Oop off=52}
                                                ; - Calculate::incArray@3 (line 15)
                                                ;   {runtime_call}
          0xb58d1b74: movl   $0x0,(%esp)
          0xb58d1b7b: call   0xb58cf290         ; OopMap{ecx=Oop off=64}
                                                ; - Calculate::incArray@3 (line 15)
                                                ;   {runtime_call}
          0xb58d1b80: nop
          0xb58d1b81: nop
          0xb58d1b82: mov    %esp,%esi
          0xb58d1b84: shr    $0xc,%esi
          0xb58d1b87: mov    0x10fee40(,%esi,4),%esi  ;   {external_word}
          0xb58d1b8e: mov    0x17c(%esi),%eax
          0xb58d1b94: movl   $0x0,0x17c(%esi)
          0xb58d1b9e: movl   $0x0,0x180(%esi)
          0xb58d1ba8: add    $0x18,%esp
          0xb58d1bab: pop    %ebp
          0xb58d1bac: jmp    0xb58cf090         ;   {runtime_call}
        [Exception Handler]
        [Stub Code]
          0xb58d1bc0: call   0xb58d06d0         ;   {no_reloc}
          0xb58d1bc5: push   $0x1098018         ;   {external_word}
          0xb58d1bca: call   0xb58d1bcf
          0xb58d1bcf: pusha
          0xb58d1bd0: call   0x00ce3650         ;   {runtime_call}
          0xb58d1bd5: hlt
        [Deopt Handler Code]
          0xb58d1bd6: push   $0xb58d1bd6        ;   {section_word}
          0xb58d1bdb: jmp    0xb5892ae0         ;   {runtime_call}
        [Verified Entry Point]
          # {method} 'incObject' '(LCalculate$MutableInteger;)V' in 'Calculate'
          # parm0:    ecx       = 'Calculate$MutableInteger'
          #           [sp+0x10]  (sp of caller)
          0xb47292c0: mov    %eax,-0x3000(%esp)
          0xb47292c7: push   %ebp
          0xb47292c8: sub    $0x8,%esp
          0xb47292ce: movl   $0x1,0x8(%ecx)     ;*synchronization entry
                                                ; - Calculate::incObject@-1 (line 19)
                                                ; implicit exception: dispatches to 0xb47292e0
          0xb47292d5: add    $0x8,%esp
          0xb47292d8: pop    %ebp
          0xb47292d9: test   %eax,0xb773e000    ;   {poll_return}
          0xb47292df: ret
          0xb47292e0: mov    $0xfffffff6,%ecx
          0xb47292e5: xchg   %ax,%ax
          0xb47292e7: call   0xb470a720         ; OopMap{off=44}
                                                ;*putfield value
                                                ; - Calculate::incObject@2 (line 19)
                                                ;   {runtime_call}
          0xb47292ec: call   0x01400630         ;*putfield value
                                                ; - Calculate::incObject@2 (line 19)
                                                ;   {runtime_call}
        [Exception Handler]
        [Stub Code]
          0xb4729300: jmp    0xb4725760         ;   {no_reloc}
        [Deopt Handler Code]
          0xb4729305: push   $0xb4729305        ;   {section_word}
          0xb472930a: jmp    0xb470bbe0         ;   {runtime_call}
          0xb472930f: .byte 0x0
With a closer look you'll find that these two methods are differ only with
          0xb58d1b4b: cmpl   $0x0,0x8(%ecx)     ; implicit exception: dispatches to 0xb58d1b6a
          0xb58d1b52: jbe    0xb58d1b74
          0xb58d1b58: movl   $0x1,0xc(%ecx)     ;*iastore

          0xb47292ce: movl   $0x1,0x8(%ecx)
which in fact is checking if we are within the bounds of an array. This is the place where we loose the 30% of the time.