Почему Java-переключатель на непрерывных ints работает быстрее с добавленными случаями?

Я работаю над некоторым Java-кодом, который нуждается в высокой оптимизации, поскольку он будет работать в горячих функциях, которые вызываются во многих точках моей основной логики программы. Часть этого кода включает в себя умножение переменных double на 10 на произвольные неотрицательные int exponent s. Один быстрый способ (отредактируйте: но не самый быстрый, см. Обновление 2 ниже), чтобы получить умноженное значение на switch на exponent:

double multiplyByPowerOfTen(final double d, final int exponent) {
   switch (exponent) {
      case 0:
         return d;
      case 1:
         return d*10;
      case 2:
         return d*100;
      // ... same pattern
      case 9:
         return d*1000000000;
      case 10:
         return d*10000000000L;
      // ... same pattern with long literals
      case 18:
         return d*1000000000000000000L;
      default:
         throw new ParseException("Unhandled power of ten " + power, 0);
   }
}

Описанные выше эллипсы показывают, что константы case int продолжают увеличиваться на 1, поэтому в приведенном выше фрагменте кода действительно 19 case. Поскольку я не был уверен, действительно ли мне понадобится все полномочия из 10 в case операторах 10 thru 18, я запустил несколько микробизнесов, сравнивающих время завершения 10 миллионов операций с этим выражением switch по сравнению с switch только с case 0 thru 9exponent, ограниченным 9 или менее, чтобы не нарушить сокращенный switch). У меня есть довольно неожиданное (по крайней мере, для меня!) Результат, что более длинный switch с более case операторами действительно работает быстрее.

На жаворонке я попытался добавить еще больше case, который только что вернул фиктивные значения, и обнаружил, что я могу заставить коммутатор работать еще быстрее с 22-27 объявленными case (хотя эти фиктивные случаи никогда на самом деле ударить, пока выполняется код). (Опять же, case добавлялись смежным образом, увеличивая прежнюю константу case на 1.) Эти разности времени выполнения не очень значительны: для случайного exponent между 0 и 10 фиктивное заполненное выражение switch завершает 10 миллионов исполнений за 1,49 секунды против 1,54 секунды для незакрепленной версии, при общей экономии 5 нс на выполнение. Таким образом, не та вещь, которая делает одержимость излишним отложением выражения switch, которое стоит усилий с точки зрения оптимизации. Но я все еще просто нахожу любопытным и контр-интуитивным, что switch не становится медленнее (или, возможно, в лучшем случае поддерживает постоянное время O (1)) для выполнения, поскольку к нему добавляется больше case.

switch benchmarking results

Это результаты, которые я получил от запуска с различными ограничениями на случайно генерируемые значения exponent. Я не включил результаты вплоть до 1 для предела exponent, но общий вид кривой остается тем же самым, с хребтом вокруг отметки 12-17, а долина между 18- 28. Все тесты выполнялись в JUnitBenchmarks с использованием общих контейнеров для случайных значений для обеспечения идентичных входных данных тестирования. Я также запускал тесты как по порядку от самого длинного оператора switch до кратчайшего, так и наоборот, чтобы попытаться устранить возможность проблем, связанных с упорядочением. Я поставил свой тестовый код на репозиторий github, если кто-то захочет воспроизвести эти результаты.

Итак, что здесь происходит? Некоторые капризы моей архитектуры или микро-эталонной конструкции? Или Java switch действительно немного быстрее выполнить в диапазоне от 18 до 28 case, чем от 11 до 17?

github test repo "switch-experiment"

ОБНОВЛЕНИЕ: Я немного очистил библиотеку бенчмаркинга и добавил текстовый файл в /results с некоторым выходом в более широком диапазоне возможных значений exponent. Я также добавил параметр в тестовом коде, чтобы не вызывать Exception из default, но это не влияет на результаты.

ОБНОВЛЕНИЕ 2: Нашел довольно неплохое обсуждение этой проблемы еще в 2009 году на форуме xkcd здесь: http://forums.xkcd.com/viewtopic.php?f=11&t=33524. Обсуждение OP с использованием Array.binarySearch() дало мне представление о простой реализации шаблона экспоненциации на основе массива выше. Нет необходимости в бинарном поиске, так как я знаю, что такое записи в array. Кажется, он работает примерно в 3 раза быстрее, чем при использовании switch, очевидно, за счет некоторого потока управления, который дает switch. Этот код был добавлен в репозиторий github.

Ответ 1

Как было указано другим ответом, поскольку значения case смежны (в отличие от разреженных), сгенерированный байт-код для ваших различных тестов использует таблицу переключателей ( инструкция по байт-коду tableswitch).

Однако, как только JIT начинает свое задание и компилирует байт-код в сборку, команда tableswitch не всегда приводит к массиву указателей: иногда таблица переключений преобразуется в то, что выглядит как lookupswitch (аналогично a if/else if).

Декомпиляция сборки, сгенерированной JIT (hotspot JDK 1.7), показывает, что она использует последовательность if/else, если в случае 17 случаев или меньше массив указателей, когда их более 18 (более эффективный).

Причина, по которой это магическое число 18 используется, похоже, доходит до значения по умолчанию MinJumpTableSize Флаг JVM (вокруг строка 352 в коде).

Я поднял проблему в списке компиляторов hotspot и похоже, это наследие прошлых тестов. Обратите внимание, что это значение по умолчанию было удалено в JDK 8 после больше бенчмаркинга было выполнено.

Наконец, когда метод становится слишком длинным ( > 25 случаев в моих тестах), он больше не привязан к настройкам JVM по умолчанию - это самая вероятная причина для снижения производительности в этой точке.


В 5 случаях декомпилированный код выглядит так (обратите внимание на инструкции cmp/je/jg/jmp, сборка для if/goto):

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x00000000024f0160: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x00000000024f0167: push   rbp
  0x00000000024f0168: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::[email protected] (line 56)
  0x00000000024f016c: cmp    edx,0x3
  0x00000000024f016f: je     0x00000000024f01c3
  0x00000000024f0171: cmp    edx,0x3
  0x00000000024f0174: jg     0x00000000024f01a5
  0x00000000024f0176: cmp    edx,0x1
  0x00000000024f0179: je     0x00000000024f019b
  0x00000000024f017b: cmp    edx,0x1
  0x00000000024f017e: jg     0x00000000024f0191
  0x00000000024f0180: test   edx,edx
  0x00000000024f0182: je     0x00000000024f01cb
  0x00000000024f0184: mov    ebp,edx
  0x00000000024f0186: mov    edx,0x17
  0x00000000024f018b: call   0x00000000024c90a0  ; OopMap{off=48}
                                                ;*new  ; - javaapplication4.Test1::[email protected] (line 83)
                                                ;   {runtime_call}
  0x00000000024f0190: int3                      ;*new  ; - javaapplication4.Test1::[email protected] (line 83)
  0x00000000024f0191: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffffa7]        # 0x00000000024f0140
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 62)
                                                ;   {section_word}
  0x00000000024f0199: jmp    0x00000000024f01cb
  0x00000000024f019b: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff8d]        # 0x00000000024f0130
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 60)
                                                ;   {section_word}
  0x00000000024f01a3: jmp    0x00000000024f01cb
  0x00000000024f01a5: cmp    edx,0x5
  0x00000000024f01a8: je     0x00000000024f01b9
  0x00000000024f01aa: cmp    edx,0x5
  0x00000000024f01ad: jg     0x00000000024f0184  ;*tableswitch
                                                ; - javaapplication4.Test1::[email protected] (line 56)
  0x00000000024f01af: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff81]        # 0x00000000024f0138
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 66)
                                                ;   {section_word}
  0x00000000024f01b7: jmp    0x00000000024f01cb
  0x00000000024f01b9: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff67]        # 0x00000000024f0128
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 68)
                                                ;   {section_word}
  0x00000000024f01c1: jmp    0x00000000024f01cb
  0x00000000024f01c3: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff55]        # 0x00000000024f0120
                                                ;*tableswitch
                                                ; - javaapplication4.Test1::[email protected] (line 56)
                                                ;   {section_word}
  0x00000000024f01cb: add    rsp,0x10
  0x00000000024f01cf: pop    rbp
  0x00000000024f01d0: test   DWORD PTR [rip+0xfffffffffdf3fe2a],eax        # 0x0000000000430000
                                                ;   {poll_return}
  0x00000000024f01d6: ret    

С 18 случаями сборка выглядит так (обратите внимание на массив указателей, который используется и подавляет необходимость всех сравнений: jmp QWORD PTR [r8+r10*1] переходит непосредственно к правильному умножению) - это вероятная причина для улучшения производительности

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x000000000287fe20: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x000000000287fe27: push   rbp
  0x000000000287fe28: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::[email protected] (line 56)
  0x000000000287fe2c: cmp    edx,0x13
  0x000000000287fe2f: jae    0x000000000287fe46
  0x000000000287fe31: movsxd r10,edx
  0x000000000287fe34: shl    r10,0x3
  0x000000000287fe38: movabs r8,0x287fd70       ;   {section_word}
  0x000000000287fe42: jmp    QWORD PTR [r8+r10*1]  ;*tableswitch
                                                ; - javaapplication4.Test1::[email protected] (line 56)
  0x000000000287fe46: mov    ebp,edx
  0x000000000287fe48: mov    edx,0x31
  0x000000000287fe4d: xchg   ax,ax
  0x000000000287fe4f: call   0x00000000028590a0  ; OopMap{off=52}
                                                ;*new  ; - javaapplication4.Test1::[email protected] (line 96)
                                                ;   {runtime_call}
  0x000000000287fe54: int3                      ;*new  ; - javaapplication4.Test1::[email protected] (line 96)
  0x000000000287fe55: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe8b]        # 0x000000000287fce8
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 92)
                                                ;   {section_word}
  0x000000000287fe5d: jmp    0x000000000287ff16
  0x000000000287fe62: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe86]        # 0x000000000287fcf0
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 90)
                                                ;   {section_word}
  0x000000000287fe6a: jmp    0x000000000287ff16
  0x000000000287fe6f: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe81]        # 0x000000000287fcf8
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 88)
                                                ;   {section_word}
  0x000000000287fe77: jmp    0x000000000287ff16
  0x000000000287fe7c: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe7c]        # 0x000000000287fd00
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 86)
                                                ;   {section_word}
  0x000000000287fe84: jmp    0x000000000287ff16
  0x000000000287fe89: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe77]        # 0x000000000287fd08
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 84)
                                                ;   {section_word}
  0x000000000287fe91: jmp    0x000000000287ff16
  0x000000000287fe96: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe72]        # 0x000000000287fd10
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 82)
                                                ;   {section_word}
  0x000000000287fe9e: jmp    0x000000000287ff16
  0x000000000287fea0: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe70]        # 0x000000000287fd18
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 80)
                                                ;   {section_word}
  0x000000000287fea8: jmp    0x000000000287ff16
  0x000000000287feaa: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe6e]        # 0x000000000287fd20
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 78)
                                                ;   {section_word}
  0x000000000287feb2: jmp    0x000000000287ff16
  0x000000000287feb4: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe24]        # 0x000000000287fce0
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 76)
                                                ;   {section_word}
  0x000000000287febc: jmp    0x000000000287ff16
  0x000000000287febe: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe6a]        # 0x000000000287fd30
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 74)
                                                ;   {section_word}
  0x000000000287fec6: jmp    0x000000000287ff16
  0x000000000287fec8: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe68]        # 0x000000000287fd38
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 72)
                                                ;   {section_word}
  0x000000000287fed0: jmp    0x000000000287ff16
  0x000000000287fed2: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe66]        # 0x000000000287fd40
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 70)
                                                ;   {section_word}
  0x000000000287feda: jmp    0x000000000287ff16
  0x000000000287fedc: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe64]        # 0x000000000287fd48
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 68)
                                                ;   {section_word}
  0x000000000287fee4: jmp    0x000000000287ff16
  0x000000000287fee6: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe62]        # 0x000000000287fd50
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 66)
                                                ;   {section_word}
  0x000000000287feee: jmp    0x000000000287ff16
  0x000000000287fef0: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe60]        # 0x000000000287fd58
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 64)
                                                ;   {section_word}
  0x000000000287fef8: jmp    0x000000000287ff16
  0x000000000287fefa: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe5e]        # 0x000000000287fd60
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 62)
                                                ;   {section_word}
  0x000000000287ff02: jmp    0x000000000287ff16
  0x000000000287ff04: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe5c]        # 0x000000000287fd68
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 60)
                                                ;   {section_word}
  0x000000000287ff0c: jmp    0x000000000287ff16
  0x000000000287ff0e: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe12]        # 0x000000000287fd28
                                                ;*tableswitch
                                                ; - javaapplication4.Test1::[email protected] (line 56)
                                                ;   {section_word}
  0x000000000287ff16: add    rsp,0x10
  0x000000000287ff1a: pop    rbp
  0x000000000287ff1b: test   DWORD PTR [rip+0xfffffffffd9b00df],eax        # 0x0000000000230000
                                                ;   {poll_return}
  0x000000000287ff21: ret    

И, наконец, сборка с 30 случаями (ниже) похожа на 18 случаев, за исключением дополнительного movapd xmm0,xmm1, который появляется в середине кода, как указано @cHao - однако наиболее вероятной причиной снижения производительности является то, что этот метод слишком длинный, чтобы быть встроенным с настройками JVM по умолчанию:

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x0000000002524560: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x0000000002524567: push   rbp
  0x0000000002524568: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::[email protected] (line 56)
  0x000000000252456c: movapd xmm1,xmm0
  0x0000000002524570: cmp    edx,0x1f
  0x0000000002524573: jae    0x0000000002524592  ;*tableswitch
                                                ; - javaapplication4.Test1::[email protected] (line 56)
  0x0000000002524575: movsxd r10,edx
  0x0000000002524578: shl    r10,0x3
  0x000000000252457c: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe3c]        # 0x00000000025243c0
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 118)
                                                ;   {section_word}
  0x0000000002524584: movabs r8,0x2524450       ;   {section_word}
  0x000000000252458e: jmp    QWORD PTR [r8+r10*1]  ;*tableswitch
                                                ; - javaapplication4.Test1::[email protected] (line 56)
  0x0000000002524592: mov    ebp,edx
  0x0000000002524594: mov    edx,0x31
  0x0000000002524599: xchg   ax,ax
  0x000000000252459b: call   0x00000000024f90a0  ; OopMap{off=64}
                                                ;*new  ; - javaapplication4.Test1::[email protected] (line 120)
                                                ;   {runtime_call}
  0x00000000025245a0: int3                      ;*new  ; - javaapplication4.Test1::[email protected] (line 120)
  0x00000000025245a1: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe27]        # 0x00000000025243d0
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 116)
                                                ;   {section_word}
  0x00000000025245a9: jmp    0x0000000002524744
  0x00000000025245ae: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe22]        # 0x00000000025243d8
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 114)
                                                ;   {section_word}
  0x00000000025245b6: jmp    0x0000000002524744
  0x00000000025245bb: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe1d]        # 0x00000000025243e0
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 112)
                                                ;   {section_word}
  0x00000000025245c3: jmp    0x0000000002524744
  0x00000000025245c8: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe18]        # 0x00000000025243e8
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 110)
                                                ;   {section_word}
  0x00000000025245d0: jmp    0x0000000002524744
  0x00000000025245d5: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe13]        # 0x00000000025243f0
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 108)
                                                ;   {section_word}
  0x00000000025245dd: jmp    0x0000000002524744
  0x00000000025245e2: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe0e]        # 0x00000000025243f8
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 106)
                                                ;   {section_word}
  0x00000000025245ea: jmp    0x0000000002524744
  0x00000000025245ef: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe09]        # 0x0000000002524400
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 104)
                                                ;   {section_word}
  0x00000000025245f7: jmp    0x0000000002524744
  0x00000000025245fc: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe04]        # 0x0000000002524408
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 102)
                                                ;   {section_word}
  0x0000000002524604: jmp    0x0000000002524744
  0x0000000002524609: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdff]        # 0x0000000002524410
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 100)
                                                ;   {section_word}
  0x0000000002524611: jmp    0x0000000002524744
  0x0000000002524616: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdfa]        # 0x0000000002524418
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 98)
                                                ;   {section_word}
  0x000000000252461e: jmp    0x0000000002524744
  0x0000000002524623: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffd9d]        # 0x00000000025243c8
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 96)
                                                ;   {section_word}
  0x000000000252462b: jmp    0x0000000002524744
  0x0000000002524630: movapd xmm0,xmm1
  0x0000000002524634: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe0c]        # 0x0000000002524448
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 92)
                                                ;   {section_word}
  0x000000000252463c: jmp    0x0000000002524744
  0x0000000002524641: movapd xmm0,xmm1
  0x0000000002524645: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffddb]        # 0x0000000002524428
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 90)
                                                ;   {section_word}
  0x000000000252464d: jmp    0x0000000002524744
  0x0000000002524652: movapd xmm0,xmm1
  0x0000000002524656: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdd2]        # 0x0000000002524430
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 88)
                                                ;   {section_word}
  0x000000000252465e: jmp    0x0000000002524744
  0x0000000002524663: movapd xmm0,xmm1
  0x0000000002524667: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdc9]        # 0x0000000002524438
                                                ;*dmul
                                                ; - javaapplication4.Test1::[email protected] (line 86)
                                                ;   {section_word}

[etc.]

  0x0000000002524744: add    rsp,0x10
  0x0000000002524748: pop    rbp
  0x0000000002524749: test   DWORD PTR [rip+0xfffffffffde1b8b1],eax        # 0x0000000000340000
                                                ;   {poll_return}
  0x000000000252474f: ret    

Ответ 2

Коммутационный регистр быстрее, если значения case помещаются в узкий диапазон Eg.

case 1:
case 2:
case 3:
..
..
case n:

Поскольку в этом случае компилятор может избежать выполнения сравнения для каждого аргумента в инструкции switch. Компилятор создает таблицу перехода, содержащую адреса действий, которые должны выполняться на разных ногах. Значение, на которое выполняется коммутатор, обрабатывается, чтобы преобразовать его в индекс в jump table. В этой реализации время, принятое в инструкции switch, намного меньше времени, затраченного на эквивалентный оператор if-else-if. Также время, указанное в инструкции switch, не зависит от числа ножек корпуса в инструкции switch.

Как указано в wikipedia о инструкции switch в разделе Компиляция.

Если диапазон входных значений определенно "мал" и имеет только несколько пробелов, некоторые компиляторы, которые включают оптимизатор, могут фактически реализовать оператор switch как таблицу ветвей или массив индексированные указатели функций вместо длинной серии условных инструкции. Это позволяет оператору switch мгновенно определять какую ветвь выполнить, не пройдя список сравнения.

Ответ 3

Ответ лежит на байт-коде:

SwitchTest10.java

public class SwitchTest10 {

    public static void main(String[] args) {
        int n = 0;

        switcher(n);
    }

    public static void switcher(int n) {
        switch(n) {
            case 0: System.out.println(0);
                    break;

            case 1: System.out.println(1);
                    break;

            case 2: System.out.println(2);
                    break;

            case 3: System.out.println(3);
                    break;

            case 4: System.out.println(4);
                    break;

            case 5: System.out.println(5);
                    break;

            case 6: System.out.println(6);
                    break;

            case 7: System.out.println(7);
                    break;

            case 8: System.out.println(8);
                    break;

            case 9: System.out.println(9);
                    break;

            case 10: System.out.println(10);
                    break;

            default: System.out.println("test");
        }
    }       
}

Соответствующий байт-код; отображаются только соответствующие детали:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   tableswitch{ //0 to 10
        0: 60;
        1: 70;
        2: 80;
        3: 90;
        4: 100;
        5: 110;
        6: 120;
        7: 131;
        8: 142;
        9: 153;
        10: 164;
        default: 175 }

SwitchTest22.java:

public class SwitchTest22 {

    public static void main(String[] args) {
        int n = 0;

        switcher(n);
    }

    public static void switcher(int n) {
        switch(n) {
            case 0: System.out.println(0);
                    break;

            case 1: System.out.println(1);
                    break;

            case 2: System.out.println(2);
                    break;

            case 3: System.out.println(3);
                    break;

            case 4: System.out.println(4);
                    break;

            case 5: System.out.println(5);
                    break;

            case 6: System.out.println(6);
                    break;

            case 7: System.out.println(7);
                    break;

            case 8: System.out.println(8);
                    break;

            case 9: System.out.println(9);
                    break;

            case 100: System.out.println(10);
                    break;

            case 110: System.out.println(10);
                    break;
            case 120: System.out.println(10);
                    break;
            case 130: System.out.println(10);
                    break;
            case 140: System.out.println(10);
                    break;
            case 150: System.out.println(10);
                    break;
            case 160: System.out.println(10);
                    break;
            case 170: System.out.println(10);
                    break;
            case 180: System.out.println(10);
                    break;
            case 190: System.out.println(10);
                    break;
            case 200: System.out.println(10);
                    break;
            case 210: System.out.println(10);
                    break;

            case 220: System.out.println(10);
                    break;

            default: System.out.println("test");
        }
    }       
}

Соответствующий байт-код; снова показаны только соответствующие части:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   lookupswitch{ //23
        0: 196;
        1: 206;
        2: 216;
        3: 226;
        4: 236;
        5: 246;
        6: 256;
        7: 267;
        8: 278;
        9: 289;
        100: 300;
        110: 311;
        120: 322;
        130: 333;
        140: 344;
        150: 355;
        160: 366;
        170: 377;
        180: 388;
        190: 399;
        200: 410;
        210: 421;
        220: 432;
        default: 443 }

В первом случае с узкими диапазонами скомпилированный байт-код использует tableswitch. Во втором случае скомпилированный байт-код использует lookupswitch.

В tableswitch целочисленное значение в верхней части стека используется для индексации в таблицу, чтобы найти цель перехода/перехода. Затем этот прыжок/ветвь выполняется немедленно. Следовательно, это операция O(1).

A lookupswitch сложнее. В этом случае целочисленное значение должно сравниваться со всеми ключами в таблице, пока не будет найден правильный ключ. После того, как ключ найден, для перехода используется цель перехода/перехода (для этого ключа привязана). Таблица, которая используется в lookupswitch, сортируется, и для поиска правильного ключа можно использовать алгоритм двоичного поиска. Производительность для двоичного поиска O(log n), а весь процесс также O(log n), потому что скачок остается O(1). Поэтому причина низкой производительности в случае разреженных диапазонов заключается в том, что правильный ключ должен быть сначала просмотрен, потому что вы не можете напрямую индексировать его в таблицу.

Если есть разреженные значения, и вы использовали только tableswitch, таблица должна содержать фиктивные записи, указывающие на параметр default. Например, если предположить, что последняя запись в SwitchTest10.java была 21 вместо 10, вы получаете:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   tableswitch{ //0 to 21
        0: 104;
        1: 114;
        2: 124;
        3: 134;
        4: 144;
        5: 154;
        6: 164;
        7: 175;
        8: 186;
        9: 197;
        10: 219;
        11: 219;
        12: 219;
        13: 219;
        14: 219;
        15: 219;
        16: 219;
        17: 219;
        18: 219;
        19: 219;
        20: 219;
        21: 208;
        default: 219 }

Таким образом, компилятор в основном создает эту огромную таблицу, содержащую фиктивные записи между пробелами, указывая на цель ветвления инструкции default. Даже если нет default, он будет содержать записи, указывающие на инструкцию после блока переключателя. Я сделал некоторые базовые тесты, и я обнаружил, что если разрыв между последним индексом и предыдущим (9) больше, чем 35, он использует lookupswitch вместо tableswitch.

Поведение оператора switch определено в Спецификация виртуальной машины Java (§3.10):

Если случаи коммутатора разрежены, представление таблицы инструкции tablewitch таблиц становится неэффективным с точки зрения пространства. Вместо этого может использоваться инструкция lookupswitch. Команда lookupswitch объединяет int-ключи (значения меток case) с целевыми смещениями в таблице. Когда выполняется команда lookupswitch, значение выражения переключателя сравнивается с клавишами в таблице. Если один из ключей соответствует значению выражения, выполнение продолжается со связанного смещения цели. Если ключ не совпадает, выполнение продолжается по умолчанию. [...]

Ответ 4

Поскольку вопрос уже ответил (более или менее), вот несколько советов. Используйте

private static final double[] mul={1d, 10d...};
static double multiplyByPowerOfTen(final double d, final int exponent) {
      if (exponent<0 || exponent>=mul.length) throw new ParseException();//or just leave the IOOBE be
      return mul[exponent]*d;
}

В этом коде используется значительно меньше IC (кеш инструкций) и всегда будет встроен. Массив будет находиться в кеше данных L1, если код горячий. Таблица поиска почти всегда выигрывает. (особенно на микрообъектах: D)

Изменить: если вы хотите, чтобы метод был hot-inlined, рассмотрите небыстрые пути, такие как throw new ParseException(), как минимум как минимум или переместите их в отдельный статический метод (следовательно, сделав их короткими как минимум). То есть throw new ParseException("Unhandled power of ten " + power, 0); является слабой идеей b/c, она питается большим количеством вложенного бюджета для кода, который может быть просто интерпретирован. Конкатенация строк довольно многословна в байт-коде. Дополнительная информация и реальный случай w/ArrayList