Использование "sincos" в Java
Во многих ситуациях мне нужен не только синус, но и косинус того же параметра.
Для C существует функция sincos
в общей библиотеке unix m
. И на самом деле, по крайней мере на i386, это должна быть одна инструкция сборки, fsincos
.
sincos, sincosf, sincosl - вычислять sin и cos одновременно
Я предполагаю, что эти преимущества существуют, потому что есть очевидное совпадение в вычислении синуса и косинуса: sin(x)^2 + cos(x)^2 = 1
. Но AFAIK не рассчитывает сократить это как cos = Math.sqrt(1 - sin*sin)
, так как функция sqrt
поставляется с аналогичной стоимостью.
Есть ли способ получить те же преимущества в Java? Думаю, я собираюсь заплатить за double[]
цену; что, возможно, делает все усилия спорными из-за добавленной коллекции мусора.
Или компилятор Hotspot достаточно умен, чтобы распознать, что мне нужны оба, и скомпилирует это с помощью команды sincos
? Могу ли я проверить, распознает ли он его, и могу ли я помочь ему распознать это, например. убедившись, что команды Math.sin
и Math.cos
являются прямыми последовательными в моем коде? Это на самом деле имеет наибольший смысл с точки зрения языка Java: с помощью компилятора это оптимизировать для использования вызова сборки fsincos
.
Собран из некоторых ассемблерных документов:
Variations 8087 287 387 486 Pentium
fsin - - 122-771 257-354 16-126 NP
fsincos - - 194-809 292-365 17-137 NP
Additional cycles required if operand > pi/4 (~3.141/4 = ~.785)
sqrt 180-186 180-186 122-129 83-87 70 NP
fsincos
должен нуждаться в дополнительной поп-музыке, но это должно быть на 1 такт. Предполагая, что CPU также не оптимизирует это, sincos
должен быть почти в два раза быстрее, чем вызов sin
дважды (второй раз для вычисления косинуса, поэтому я считаю, что ему нужно будет добавить дополнение). sqrt
может быть быстрее в некоторых ситуациях, но синус может быть быстрее.
Обновление. Я провел несколько экспериментов на C, но они неубедительны. Интересно, что sincos
кажется еще немного быстрее, чем sin
(без cos
), и компилятор GCC будет использовать fsincos
при вычислении как sin
, так и cos
- поэтому он делает то, d нравится Hotspot делать (или тоже Hotspot?). Я еще не мог помешать компилятору перехитрить меня, используя fsincos
, но не используя cos
. Затем он вернется к C sin
, а не к fsin
.
Ответы
Ответ 1
Я выполнил некоторые микрообъективы с суппортом. 10000000 итераций над (предварительно вычисленным) массивом случайных чисел в диапазоне -4 * pi.. 4 * pi. Я изо всех сил старался получить самое быстрое решение JNI, которое я мог бы придумать, - немного сложно предсказать, действительно ли вы получите fsincos
или некоторый эмулированный sincos
. Зарегистрированные цифры являются лучшими из 10 испытаний суппорта (которые, в свою очередь, состоят из 3-10 испытаний, среднее из которых сообщается). Так что примерно 30-100 пробегов внутреннего цикла.
Я сравнивал несколько вариантов:
-
Math.sin
только (ссылка)
-
Math.cos
только (ссылка)
-
Math.sin
+ Math.cos
-
sincos
через JNI
-
Math.sin
+ cos через Math.sqrt( (1+sin) * (1-sin) )
+ реконструкция знака
-
Math.cos
+ sin через Math.sqrt( (1+cos) * (1-cos) )
+ реконструкция знака
(1+sin)*(1-sin)=1-sin*sin
математически, но если sin близок к 1, он должен быть более точным? Разница во времени минимальна, вы сохраняете одно дополнение.
Подпишите реконструкцию через x %= TWOPI; if (x<0) x+=TWOPI;
, а затем проверите квадрант. Если у вас есть идея, как сделать это с меньшим количеством процессоров, я буду рад услышать.
Числовые потери через sqrt
кажутся в порядке, по крайней мере, для общих углов. В диапазоне 1е-10 от грубых экспериментов.
Sin 1,30 ==============
Cos 1,29 ==============
Sin, Cos 2,52 ============================
JNI sincos 1,77 ===================
SinSqrt 1,49 ================
CosSqrt 1,51 ================
sqrt(1-s*s)
против sqrt((1+s)*(1-s))
составляет около 0,01 разницы. Как вы можете видеть, подход на основе sqrt
выигрывает руки против любого из других (поскольку в настоящее время мы не можем получить доступ к sincos
в чистой Java). JNI sincos
лучше, чем вычисление sin
и cos
, но подход sqrt
еще быстрее. cos
сам по себе, как правило, является тиканием (0,01) лучше, чем sin
, но различие в случае восстановления знака имеет дополнительный тест >
. Я не думаю, что мои результаты подтверждают, что либо sin+sqrt
, либо cos+sqrt
явно предпочтительнее, но они сохраняют около 40% времени по сравнению с sin
, а затем cos
.
Если бы мы расширили Java, чтобы иметь встроенный оптимизированный sincos, то это, вероятно, будет еще лучше. ИМХО это обычный вариант использования, например. в графике. При использовании в AWT, Batik и т.д. Многочисленные приложения могут извлечь из этого выгоду.
Если я запустил это снова, я бы добавил JNI sin
и noop
для оценки стоимости JNI. Возможно, также можно сравнить трюк sqrt
через JNI. Просто чтобы убедиться, что мы действительно хотим встроенный sincos
в конечном итоге.
Ответ 2
Большинство вычислений sin и cos - это вызовы непосредственно на аппаратное обеспечение. Существует не так много более быстрого способа вычислить его, чем это. В частности, в диапазоне + - pi/4 ставки очень быстры. Если вы используете аппаратное ускорение в целом и пытаетесь ограничить значения указанными, вы должны быть в порядке. Источник.
Ответ 3
Вы всегда можете просмотреть профиль.
Как правило, sqrt должен иметь такую же скорость, что и деление, поскольку внутренняя реализация div и sqrt очень похожа.
Sin и косинус OTOH вычисляются с помощью полиномов до 10 градусов без каких-либо общих коэффициентов и, возможно, с уменьшением по модулю 2pi - это единственная общая часть, разделяемая в sincos (когда не используется CORDIC).
EDIT Пересмотренное профилирование (с исправлением опечатки) показывает временную разницу для
sin+cos: 1.580 1.580 1.840 (time for 200M iterations, 3 successive trials)
sincos: 1.080 0.900 0.920
sin+sqrt: 0.870 1.010 0.860
Ответ 4
Глядя на код Hotspot, я уверен, что Oracle Hotspot VM не оптимизирует sin (a) + cos (a) в fsincos: См. assembler_x86.cpp, строка 7482ff.
Однако я бы заподозрил, что увеличение числа машинных циклов для использования fsin и fcos отдельно легко опережает другие операции, такие как запуск GC. Я бы использовал стандартные функции Java и профайл приложения. Только если прогон профиля показывает, что значительное время тратится на вызовы sin/cos, я бы рискнул сделать что-то с этим.
В этом случае я бы создал обертку JNI, которая использует 2-элементный jdoublearray как параметр out. Если у вас есть только один поток, который использует операции JNI sincos, вы можете использовать статически инициализированный двойной массив [2] в вашем Java-коде, который будет повторно использоваться снова и снова.
Ответ 5
В обычной Java нет fsincos. Кроме того, версия JNI может быть медленнее, чем двойной вызов java.lang.Math.sin() и cos().
Я думаю, вы обеспокоены скоростью sin (x)/cos (x). Поэтому я даю вам предложение для быстрых тригонометрических операций, взамен fsincos: Look Up Table. Ниже мой оригинальный пост. Надеюсь, это поможет вам.
=====
Я попытался добиться наилучшей производительности тригонометрических функций (sin и cos), используя Look Up Tables (LUT).
Что я нашел:
- LUT может быть 20-25 раз быстрее, чем java.lang.Math.sin()/cos(). Возможно так же быстро, как native fsin/fcos. Может быть, так же быстро, как fsincos.
- Но java.lang.Math.sin() и cos() FASTER, чем любой другой способ вычисления sin/cos, если вы используете углы между 0 и 45 градусами;
-
Но заметим, что углы ниже 12 ° имеют sin (x) почти == x. Это еще быстрее,
-
В некоторых реализациях используется массив float для хранения sin и другой для cos. Это не нужно. Просто помните, что:
cos(x) == sin(x + PI/2)
- То есть, если у вас есть таблица sin (x), у вас есть таблица cos (x) бесплатно.
Я провел несколько тестов с sin() для углов в диапазоне [0..45], используя java.lang.Math.sin(); таблица наивного поиска на 360 позиций, оптимизированный LUT90 со значениями таблицы для диапазона [0..90], но расширенный для работы с [0..360]; и Посмотрите таблицу с интерполяцией. Обратите внимание, что после предупреждения java.lang.Math.sin() быстрее других:
Size test: 10000000
Angles range: [0.0...45.0]
Time in ms
Trial | Math.sin() | Lut sin() | LUT90.sin() | Lut sin2() [interpolation]
0 312,5879 25,2280 27,7313 36,4127
1 12,9468 19,5467 21,9396 34,2344
2 7,6811 16,7897 18,9646 32,5473
3 7,7565 16,7022 19,2343 32,8700
4 7,6634 16,9498 19,6307 32,8087
Источники, доступные здесь GitHub
Но если вам нужна высокая производительность в диапазоне [-360..360], java.lang.Math lib работает медленнее. Таблица Look up (LUT) примерно в 20 раз быстрее. Если требуется высокая точность, вы можете использовать LUT с интерполяцией, она немного медленнее, но все же быстрее, чем java.lang.Math. См. Мой sin2() в Math2.java, по ссылке выше.
Ниже приведены номера для большого диапазона углов:
Size test: 10000000
Angles range: [-360.0...360.0]
Time in ms
Trial|Math.sin() | Lut sin() | LUT90.sin() | Lut.sin2() [interpolation]
0 942,7756 35,1488 47,4198 42,9466
1 915,3628 28,9924 37,9051 41,5299
2 430,3372 24,8788 34,9149 39,3297
3 428,3750 24,8316 34,5718 39,5187