Я работаю над некоторым 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 9
(с exponent
, ограниченным 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
.
Это результаты, которые я получил от запуска с различными ограничениями на случайно генерируемые значения 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.