Ответ 1
Вы не должны заботиться о байт-коде, поскольку современные JVM достаточно умны, чтобы скомпилировать как lookupswitch
, так и tableswitch
таким же эффективным способом.
Интуитивно tableswitch
должен быть быстрее, и это также предлагается
Спецификация JVM:
Таким образом, команда tableswitch, вероятно, более эффективна, чем поисковый переключатель, в котором соображения пространства допускают выбор.
Однако спецификация была написана еще 20 лет назад, когда JVM не имела компилятора JIT. Есть ли разница в производительности в современной JVM HotSpot?
Тест
package bench;
import org.openjdk.jmh.annotations.*;
@State(Scope.Benchmark)
public class SwitchBench {
@Param({"1", "2", "3", "4", "5", "6", "7", "8"})
int n;
@Benchmark
public long lookupSwitch() {
return Switch.lookupSwitch(n);
}
@Benchmark
public long tableSwitch() {
return Switch.tableSwitch(n);
}
}
Чтобы иметь точный контроль над байт-кодом, я строю класс Switch
с Jasmin.
.class public bench/Switch
.super java/lang/Object
.method public static lookupSwitch(I)I
.limit stack 1
iload_0
lookupswitch
1 : One
2 : Two
3 : Three
4 : Four
5 : Five
6 : Six
7 : Seven
default : Other
One:
bipush 11
ireturn
Two:
bipush 22
ireturn
Three:
bipush 33
ireturn
Four:
bipush 44
ireturn
Five:
bipush 55
ireturn
Six:
bipush 66
ireturn
Seven:
bipush 77
ireturn
Other:
bipush -1
ireturn
.end method
.method public static tableSwitch(I)I
.limit stack 1
iload_0
tableswitch 1
One
Two
Three
Four
Five
Six
Seven
default : Other
One:
bipush 11
ireturn
Two:
bipush 22
ireturn
Three:
bipush 33
ireturn
Four:
bipush 44
ireturn
Five:
bipush 55
ireturn
Six:
bipush 66
ireturn
Seven:
bipush 77
ireturn
Other:
bipush -1
ireturn
.end method
Результаты не показывают разницы в производительности между показателями lookupswitch/tableswitch, но есть небольшое изменение в зависимости от аргумента switch:
Сборка
Проверим теорию, посмотрев на сгенерированный ассемблерный код.
Следующая опция JVM поможет: -XX:CompileCommand=print,bench.Switch::*
# {method} {0x0000000017498a48} 'lookupSwitch' '(I)I' in 'bench/Switch'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x000000000329b240: sub $0x18,%rsp
0x000000000329b247: mov %rbp,0x10(%rsp) ;*synchronization entry
; - bench.Switch::[email protected]
0x000000000329b24c: cmp $0x4,%edx
0x000000000329b24f: je 0x000000000329b2a5
0x000000000329b251: cmp $0x4,%edx
0x000000000329b254: jg 0x000000000329b281
0x000000000329b256: cmp $0x2,%edx
0x000000000329b259: je 0x000000000329b27a
0x000000000329b25b: cmp $0x2,%edx
0x000000000329b25e: jg 0x000000000329b273
0x000000000329b260: cmp $0x1,%edx
0x000000000329b263: jne 0x000000000329b26c ;*lookupswitch
; - bench.Switch::[email protected]
...
Здесь мы видим двоичный поиск, начинающийся со среднего значения 4 (это объясняет, почему случай 4 имеет лучшую производительность на графике выше).
Но интересно то, что tableswitch
скомпилируется точно так же!
# {method} {0x0000000017528b18} 'tableSwitch' '(I)I' in 'bench/Switch'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x000000000332c280: sub $0x18,%rsp
0x000000000332c287: mov %rbp,0x10(%rsp) ;*synchronization entry
; - bench.Switch::[email protected]
0x000000000332c28c: cmp $0x4,%edx
0x000000000332c28f: je 0x000000000332c2e5
0x000000000332c291: cmp $0x4,%edx
0x000000000332c294: jg 0x000000000332c2c1
0x000000000332c296: cmp $0x2,%edx
0x000000000332c299: je 0x000000000332c2ba
0x000000000332c29b: cmp $0x2,%edx
0x000000000332c29e: jg 0x000000000332c2b3
0x000000000332c2a0: cmp $0x1,%edx
0x000000000332c2a3: jne 0x000000000332c2ac ;*tableswitch
; - bench.Switch::[email protected]
...
Таблица перехода
Но подождите... Почему двоичный поиск, а не таблица перехода?
JVM HotSpot имеет эвристику для генерации таблицы перехода только для коммутаторов с 10+ случаями. Это может быть изменено опцией -XX:MinJumpTableSize=
.
ОК, позвольте расширить наш тестовый пример еще несколькими ярлыками и снова проверить сгенерированный код.
# {method} {0x0000000017288a68} 'lookupSwitch' '(I)I' in 'bench/Switch'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x000000000307ecc0: sub $0x18,%rsp ; {no_reloc}
0x000000000307ecc7: mov %rbp,0x10(%rsp) ;*synchronization entry
; - bench.Switch::[email protected]
0x000000000307eccc: mov %edx,%r10d
0x000000000307eccf: dec %r10d
0x000000000307ecd2: cmp $0xa,%r10d
0x000000000307ecd6: jb 0x000000000307ece9
0x000000000307ecd8: mov $0xffffffff,%eax
0x000000000307ecdd: add $0x10,%rsp
0x000000000307ece1: pop %rbp
0x000000000307ece2: test %eax,-0x1faece8(%rip) # 0x00000000010d0000
; {poll_return}
0x000000000307ece8: retq
0x000000000307ece9: movslq %edx,%r10
0x000000000307ecec: movabs $0x307ec60,%r11 ; {section_word}
0x000000000307ecf6: jmpq *-0x8(%r11,%r10,8) ;*lookupswitch
; - bench.Switch::[email protected]
^^^^^^^^^^^^^^^^^^^^^^^^^
Да! Вот наша вычисленная команда перехода. Обратите внимание, что это генерируется для lookupswitch
. Но для tableswitch
будет точно такой же.
Удивительно, что JSM HotSpot может генерировать таблицы перехода даже для переключателей с разрывами и с выбросами. -XX:MaxJumpTableSparseness
определяет, насколько велики пробелы. Например. если есть метки от 1 до 10, то от 13 до 20, а последняя метка со значением 99 - JIT будет генерировать проверочный тест для значения 99, а для остальных меток - создать таблицу.
Исходный код
Исходный код HotSpot, наконец, убедится, что не должно быть разницы в производительности между lookupswitch
и tableswitch
после того, как метод JIT-скомпилирован с C2. Это в основном потому, что синтаксический анализ обеих команд заканчивается вызовом той же функции jump_switch_ranges
, которая работает для произвольного набора меток.
Заключение
Как мы видели, JVM HotSpot может скомпилировать tableswitch
с помощью двоичного поиска и lookupswitch
с помощью таблицы переходов или наоборот. Это зависит от количества и плотности меток, но не от самого байткода.
Итак, отвечая на ваш оригинальный вопрос - , вам не нужно!