Совсем недавно уважаемый lany написал замечательный пост про mutable numbers в Java: http://habrahabr.ru/post/151887/
В комментарии к его посту я упомянул, что если важна производительность, то можно заменить объект-обертку на одно-элементный массив, доступ к элементу массива по определению быстрее чем извлечение значения из instance field.
Этот стереотип мне достался в наследство от Sun после прочтения очередного performance white paper. Там было написано что быстрее всего происходит доступ к локальной переменной, следом идет статический филд, потом элемент массива и замыкает этот список instance field.
К счастью на слово мне не поверили и это послужило поводом для написания этой статьи.
Статья не расчитана на джуниоров, читатель должен знать Java, ASM x86 и байткод.
Для того чтобы понять быстрее будет или нет доступ к элементу массива в сравнении с доступом к полю объекта можно:
Я выбрал второй путь, от синтетических тестов уже тошнит. Для начала накидаем тестовый класс.
Класс IntNumField работает с полем объекта, а IntNumArray — как я предложил, с одноэлементным массивом. Каждый из классов содержит метод inc который инкрементит на единицу наш mutable. В методах test прогоняется вызов каждой версии inc 10,000 раз — чтобы метод гарантированно скомпилировался.
Но для начала взглянем на байткод:
В методе get все просто, грузим this и кладем в стек значение value. В остальных примерно тоже самое, ну разве что inc использует dup для оптимизации.
Теперь посмотрим что нас ждет в классе IntNumArray:
Примерно тоже самое, разве что getfield заменен на iconst_0 и iaload. Что же, пока все равно непонятно, поэтому взглянем по ассемблер который сгенерирует HotSpot для методов inc с помощью специальных параметров:
Мне пришлось урезать оба метода выкинув такие вещи как code alignment и кучу кода в секции [Exception Handler].
Если всмотреться, оба метода используют inc %esi для инкремента значения на стеке. Отличаются только
и
Первые две строки во втором кусочке кода это проверка того что индекс 0 не выходит за границы массива (0 < array.length). Вот из-за нее и получается что я был неправ, Хуже того, точно такая же проверка происходит двумя строчками ниже… Вряд ли это будет медленнее, но уж точно не будет быстрее чем доступ к полю объекта.
Спасибо apangin за то что усомнился, я думаю это интересно не только нам двоим. Можно спокойно использовать класс-обертку и все прелести ООП.
P.S. Я все-таки запустил performance benchmark, никакой разницы.
В комментарии к его посту я упомянул, что если важна производительность, то можно заменить объект-обертку на одно-элементный массив, доступ к элементу массива по определению быстрее чем извлечение значения из instance field.
Этот стереотип мне достался в наследство от Sun после прочтения очередного performance white paper. Там было написано что быстрее всего происходит доступ к локальной переменной, следом идет статический филд, потом элемент массива и замыкает этот список instance field.
К счастью на слово мне не поверили и это послужило поводом для написания этой статьи.
Статья не расчитана на джуниоров, читатель должен знать Java, ASM x86 и байткод.
Для того чтобы понять быстрее будет или нет доступ к элементу массива в сравнении с доступом к полю объекта можно:
- Написать синтетический performance test
- Разобраться что стоит на самом деле за array[0] и this.value
Я выбрал второй путь, от синтетических тестов уже тошнит. Для начала накидаем тестовый класс.
package numbers; public class TestNumbers { public static void main(String[] args) { test(new IntNumField()); test(IntNumArray.create()); } private static final void test(int[] mutableInt2) { for (int index = 10000; index-- > 0;) { IntNumArray.inc(mutableInt2); } } private static final void test(IntNumField mutableInt1) { for (int index = 10000; index-- > 0;) { mutableInt1.inc(); } } public static final class IntNumField { protected int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; } public final void inc() { this.value++; } } public static final class IntNumArray { public static final int[] create() { return new int[1]; } public static final int get(int[] array) { return array[0]; } public static final void set(int[] array, int value) { array[0] = value; } public static final void inc(int[] array) { array[0]++; } } }
Класс IntNumField работает с полем объекта, а IntNumArray — как я предложил, с одноэлементным массивом. Каждый из классов содержит метод inc который инкрементит на единицу наш mutable. В методах test прогоняется вызов каждой версии inc 10,000 раз — чтобы метод гарантированно скомпилировался.
Но для начала взглянем на байткод:
~$ javap -c numbers/TestNumbers$IntNumField public int getValue(); Code: 0: aload_0 1: getfield #18; //Field value:I 4: ireturn public void setValue(int); Code: 0: aload_0 1: iload_1 2: putfield #18; //Field value:I 5: return public final void inc(); Code: 0: aload_0 1: dup 2: getfield #18; //Field value:I 5: iconst_1 6: iadd 7: putfield #18; //Field value:I 10: return
В методе get все просто, грузим this и кладем в стек значение value. В остальных примерно тоже самое, ну разве что inc использует dup для оптимизации.
Теперь посмотрим что нас ждет в классе IntNumArray:
~$ javap -c numbers/TestNumbers$IntNumArray public static final int get(int[]); Code: 0: aload_0 1: iconst_0 2: iaload 3: ireturn public static final void set(int[], int); Code: 0: aload_0 1: iconst_0 2: iload_1 3: iastore 4: return public static final void inc(int[]); Code: 0: aload_0 1: iconst_0 2: dup2 3: iaload 4: iconst_1 5: iadd 6: iastore 7: return
Примерно тоже самое, разве что getfield заменен на iconst_0 и iaload. Что же, пока все равно непонятно, поэтому взглянем по ассемблер который сгенерирует HotSpot для методов inc с помощью специальных параметров:
~$ java -XX:+UnlockDiagnosticVMOptions -XX:CompileThreshold=10000 -XX:+PrintAssembly numbers.TestNumbers # {method} 'inc' '()V' in 'numbers/TestNumbers$IntNumField' # [sp+0x20] (sp of caller) 0x02141a87: cmp 0x4(%ecx),%eax 0x02141a8a: jne 0x020dd100 ; {runtime_call} [Verified Entry Point] 0x02141a90: mov %eax,0xffffc000(%esp) 0x02141a97: push %ebp 0x02141a98: sub $0x18,%esp ;*aload_0 ; - numbers.TestNumbers$IntNumField::inc@0 (line 50) 0x02141a9b: mov 0x8(%ecx),%esi ;*getfield value ; - numbers.TestNumbers$IntNumField::inc@2 (line 50) 0x02141a9e: inc %esi 0x02141a9f: mov %esi,0x8(%ecx) ;*putfield value ; - numbers.TestNumbers$IntNumField::inc@7 (line 50) 0x02141aa2: add $0x18,%esp 0x02141aa5: pop %ebp 0x02141aa6: test %eax,0x1b0100 ; {poll_return} 0x02141aac: ret # {method} 'inc' '([I)V' in 'numbers/TestNumbers$IntNumArray' # parm0: ecx = '[I' # [sp+0x20] (sp of caller) 0x02141c80: mov %eax,0xffffc000(%esp) 0x02141c87: push %ebp 0x02141c88: sub $0x18,%esp ;*aload_0 ; - numbers.TestNumbers$IntNumArray::inc@0 (line 71) 0x02141c8b: cmpl $0x0,0x8(%ecx) ; implicit exception: dispatches to 0x02141cb7 0x02141c92: jbe 0x02141cc1 0x02141c98: mov 0xc(%ecx),%esi ;*iaload ; - numbers.TestNumbers$IntNumArray::inc@3 (line 71) 0x02141c9b: inc %esi 0x02141c9c: cmpl $0x0,0x8(%ecx) 0x02141ca3: jbe 0x02141ccd 0x02141ca9: mov %esi,0xc(%ecx) ;*iastore ; - numbers.TestNumbers$IntNumArray::inc@6 (line 71) 0x02141cac: add $0x18,%esp 0x02141caf: pop %ebp 0x02141cb0: test %eax,0x1b0100 ; {poll_return} 0x02141cb6: ret
Мне пришлось урезать оба метода выкинув такие вещи как code alignment и кучу кода в секции [Exception Handler].
Если всмотреться, оба метода используют inc %esi для инкремента значения на стеке. Отличаются только
mov 0x8(%ecx),%esi ;*getfield value
и
cmpl $0x0,0x8(%ecx) ; implicit exception: dispatches to 0x02141cb7 jbe 0x02141cc1 mov 0xc(%ecx),%esi ;*iaload
Первые две строки во втором кусочке кода это проверка того что индекс 0 не выходит за границы массива (0 < array.length). Вот из-за нее и получается что я был неправ, Хуже того, точно такая же проверка происходит двумя строчками ниже… Вряд ли это будет медленнее, но уж точно не будет быстрее чем доступ к полю объекта.
Спасибо apangin за то что усомнился, я думаю это интересно не только нам двоим. Можно спокойно использовать класс-обертку и все прелести ООП.
P.S. Я все-таки запустил performance benchmark, никакой разницы.
