Подтвердить что ты не робот

Использование "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.

4b9b3361

Ответ 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