Тригонометрия в бенчмарках
“Неважно, что ты любишь больше:
косинус ли, синус ли…”
Тригонометрия – основа многих приложений, от компьютерной графики до научных симуляций. Все мы привыкли вызывать sin
и cos
, не задумываясь, как они реализованы. А реализации могут быть разные! Работая над математической библиотекой для dlib2, я провел интересное исследование – какая тригонометрия лучше? Конечно, есть функции из std.math
, и в большинстве случаев подойдут именно они. Но не все так просто – все зависит от того, что именно вы разрабатываете.
Если вы собираете обычное приложение, то кажется, что беспокоиться не о чем. Но если вам, по тем или иным причинам, нельзя обращаться к Phobos? Тогда есть два основных пути – sin
и cos
из стандартной библиотеки C, либо кастомная реализация, если код собирается под голое железо (например, при создании ядра ОС или программировании встраиваемой электроники). Но если вы используете LDC, то ничто не мешает использовать интринсики LLVM – они, оказывается, работают быстрее, чем std.math
!
Я провел ряд тестов для всех вариантов тригонометрии:
- Тест на точность – вычисление синуса и косинуса для 200 аргументов от -π до +π. Замерялась максимальная погрешность – расхождение результата с std.math.sin и std.math.cos;
- Тест на производительность – время вычисления синуса и косинуса 1000000 раз.
Во всех кейсах я использовал LDC 1.39.0 под Windows 10. Получилось следующее:
std.math.sin
,std.math.cos
:- Время выполнения: 4 мс
- LLVM интринсики
llvm_sin
,llvm_cos
:- Время выполнения: 2 мс
- Точность: абсолютная (макс. погрешность для
sin
: 0, дляcos
: 0)
- Функции sin, cos из стандартной библиотеки C:
- Время выполнения: 21 мс
- Точность: абсолютная (макс. погрешность для
sin
: 0, дляcos
: 0)
- Моя кастомная реализация на таблицах:
- Время выполнения: 33 мс
- Точность: порядка 10-7 (макс. погрешность для
sin
: 2.97038e-07, дляcos
: 1.78188e-07)
Также я пробовал версию с ассемблерными вставками, но она получилась почему-то медленнее кастомной – видимо, при использовании инлайнового ассемблера компилятор не задействует какие-то оптимизации (а еще есть мнение, что x87 fsin
, fcos
на современных процессорах медленные сами по себе). Смысла в таком варианте реализации особо нет, так что я его не стал рассматривать для включения в библиотеку.
В итоге в dlib2 войдут четыре реализации с таким приоритетом:
- Если используется LDC, то синус и косинус – это интринсики (то есть, кодогенератор сам выбирает оптимальную реализацию под нужную архитектуру);
- Если используются другие компиляторы (DMD, GDC):
- Если код компилируется с поддержкой Phobos, то используются функции из
std.math
; - Если код собирается в режиме version(
NoPhobos)
, но неversion(FreeStanding)
(то есть, под Windows или Unix-подобную ОС), то используются функции рантайма C; - Если же идет компиляция в bare metal, то используется кастомная реализация на таблицах.
- Если код компилируется с поддержкой Phobos, то используются функции из