Каков самый быстрый способ вычислить грех и cos вместе?
Я хотел бы вычислить как синус, так и ко-синус значения вместе (например, для создания матрицы вращения). Конечно, я мог бы вычислить их отдельно друг за другом, как a = cos(x); b = sin(x);
, но мне интересно, есть ли более быстрый способ, когда нужны оба значения.
Edit: Обобщить ответы до сих пор:
Влад сказал, что есть команда asm
FSINCOS
вычисление обоих из них (почти в то же время, что и a звоните в )как Чи замечено, что эта оптимизация иногда уже выполняется компилятором (при использовании флагов оптимизации).
caf указано, что функции
sincos
иsincosf
вероятно, доступны и могут быть вызваны непосредственно, просто включаяmath.h
tanascius подход использования таблицы поиска обсуждается спорным. (Однако на моем компьютере и в тестовом сценарии он работает в 3 раза быстрее, чем
sincos
С почти такой же точностью 32-битной плавающей точки.)Джоэл Гудвин связано с интересным подходом чрезвычайно быстрой техники аппроксимации с довольно хорошей точностью (для меня это еще быстрее, чем поиск таблицы)
18 ответов:
современные процессоры Intel / AMD имеют инструкцию
FSINCOS
для вычисления функций синуса и Косинуса одновременно. Если вам нужна сильная оптимизация, возможно, вам следует ее использовать.вот небольшой пример:http://home.broadpark.no / ~alein/fsincos.html
вот еще один пример (для MSVC): http://www.codeguru.com/forum/showthread.php?t=328669
вот еще один пример (с GCC): http://www.allegro.cc/forums/thread/588470
надеюсь, что один из них поможет. (Я сам не использовал эту инструкцию, извините.)
поскольку они поддерживаются на уровне процессора, я ожидаю, что они будут намного быстрее, чем поиск таблиц.
Edit:
Википедия говорит о том, чтоFSINCOS
был добавлен на 387 процессоров, так что вы вряд ли можете найти процессор, который не поддерживает его.Edit:
Intel документация утверждает, чтоFSINCOS
примерно в 5 раз медленнее, чемFDIV
(т. е. деление с плавающей запятой).Edit:
Обратите внимание, что не все современные компиляторы оптимизируют вычисление синуса и Косинуса в вызовFSINCOS
. В частности, мой VS 2008 не сделал этого таким образом.Edit:
Первый пример ссылки мертв, но есть все еще версия на машине Wayback.
современные процессоры x86 имеют инструкцию fsincos, которая будет делать именно то, что вы просите - вычислять sin и cos одновременно. Хороший оптимизирующий компилятор должен обнаружить код, который вычисляет sin и cos для одного и того же значения, и использовать команду fsincos для выполнения этого.
для этого потребовалось некоторое скручивание флагов компилятора, но:
$ gcc --version i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5488) Copyright (C) 2005 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ cat main.c #include <math.h> struct Sin_cos {double sin; double cos;}; struct Sin_cos fsincos(double val) { struct Sin_cos r; r.sin = sin(val); r.cos = cos(val); return r; } $ gcc -c -S -O3 -ffast-math -mfpmath=387 main.c -o main.s $ cat main.s .text .align 4,0x90 .globl _fsincos _fsincos: pushl %ebp movl %esp, %ebp fldl 12(%ebp) fsincos movl 8(%ebp), %eax fstpl 8(%eax) fstpl (%eax) leave ret .subsections_via_symbols
Tada, он использует инструкцию fsincos!
когда вам нужна производительность, вы можете использовать предварительно рассчитанную таблицу sin/cos (одна таблица будет сделана, сохранена в виде словаря). Ну, это зависит от точности вам нужно (может быть, таблица будет большой), но это должно быть очень быстро.
технически, вы бы добиться этого с помощью комплексных чисел и Формула Эйлера. Таким образом, что-то вроде (C++)
complex<double> res = exp(complex<double>(0, x)); // or equivalent complex<double> res = polar<double>(1, x); double sin_x = res.imag(); double cos_x = res.real();
должен дать вам синус и косинус в один шаг. Как это делается внутри-это вопрос использования компилятора и библиотеки. Это может (и может) занять больше времени, чтобы сделать это таким образом (просто потому, что Формула Эйлера в основном используется для вычисления комплекса
exp
С помощьюsin
иcos
– и не наоборот), но там может возможна некоторая теоретическая оптимизация.
Edit
заголовки в
<complex>
для GNU C++ 4.2 используются явные вычисленияsin
иcos
внутриpolar
, так что это не выглядит слишком хорошо для оптимизации там, если компилятор не делает некоторую магию (см.-ffast-math
и-mfpmath
переключатели как написано в Чи).
вы можете вычислить либо, а затем использовать идентификатор:
cos(x)2 = 1 - sin(x)2но, как говорит @tanascius, предварительно вычисленная таблица - это путь.
если вы используете библиотеку GNU C, то вы можете сделать:
#define _GNU_SOURCE #include <math.h>
и вы получите заявления
sincos()
,sincosf()
иsincosl()
функции, которые вычисляют оба значения вместе-предположительно самым быстрым способом для вашей целевой архитектуры.
многие математические библиотеки C, как указывает caf, уже имеют sincos(). Заметным исключением является MSVC.
- У Sun был sincos () по крайней мере с 1987 года (двадцать три года; у меня есть печатная страница man)
- HPUX 11 было это в 1997 году (но не в HPUX 10.20)
- добавлено в glibc в версии 2.1 (февраль 1999)
- стал встроенным в gcc 3.4 (2004), __builtin_sincos ().
и что касается поиска, Эрик С. Раймонд в Искусство программирования Unix (2004) (Глава 12) прямо говорит, что это плохая идея (в настоящий момент времени):
" Другим примером является предварительное вычисление небольших таблиц-например, таблицы sin (x) по степени оптимизации вращений в 3D графическом движке будет возьмите 365 × 4 байта на современной машине. Прежде чем процессоры получили достаточно быстрее, чем память, чтобы требовать кэширования, это было очевидной скорости оптимизация. В настоящее время это может быть быстрее, чтобы пересчитать каждый времени, а чем платить за процент дополнительных промахов кэша, вызванных стол.
но в будущем, это может обернуться снова, как кэши становятся больше. В более общем плане, многие оптимизации являются временными и могут легко поворачиваться в пессимизацию по мере изменения соотношения затрат. Единственный способ узнать это измерьте и увидите.(от Искусство программирования Unix)
но, судя по обсуждению выше, не все согласится.
на этой странице форума есть очень интересный материал, который ориентирован на поиск хороших приближений, которые являются быстрыми: http://www.devmaster.net/forums/showthread.php?t=5784
отказ от ответственности: не использовал ни один из этих вещей сам.
обновление 22 Feb 2018: Wayback Machine-это единственный способ посетить исходную страницу: https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/fast-and-accurate-sine-cosine
Я не верю, что таблицы являются хорошей идеей для этой проблемы. Если ваши требования к точности не очень низки, таблица должна быть очень большой. И современные процессоры могут делать много вычислений, пока значение извлекается из основной памяти. Это не один из тех вопросов, на которые можно правильно ответить аргументом (Даже не моим), проверить и измерить и рассмотреть данные.
но я бы посмотрел на быстрые реализации SinCos, которые вы найдете в библиотеках таких как ACML AMD и MKL Intel.
Если вы готовы использовать коммерческий продукт и одновременно вычисляете ряд вычислений sin/cos (поэтому вы можете использовать векторные функции), вы должны проверить математическая библиотека ядра Intel.
Он имеет функции синусно-косинусный
согласно этой документации, он составляет в среднем 13.08 часов / элемент на core 2 duo в режиме высокой точности, который, я думаю, будет еще быстрее, чем fsincos.
в этой статье показано, как построить параболический алгоритм, который генерирует как синус, так и косинус:
DSP трюк: одновременное параболическое приближение Sin и Cos
http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos
когда производительность критична для такого рода вещей, нет ничего необычного в том, чтобы ввести таблицу поиска.
для творческого подхода, как насчет расширения серии Тейлора? Поскольку они имеют похожие термины, вы можете сделать что-то вроде следующего псевдо:
numerator = x denominator = 1 sine = x cosine = 1 op = -1 fact = 1 while (not enough precision) { fact++ denominator *= fact numerator *= x cosine += op * numerator / denominator fact++ denominator *= fact numerator *= x sine += op * numerator / denominator op *= -1 }
это означает, что вы делаете что - то вроде этого: начиная с x и 1 для греха и Косинуса, следуйте шаблону-вычитайте x^2 / 2! из Косинуса вычтите x^3 / 3! от синуса, добавьте x^4 / 4! к Косинусу добавьте x^5 / 5! до синуса...
Я понятия не имею, будет ли это эффективным. Если вам нужна меньшая точность, чем встроенная в Sin() и cos() дать вам, это может быть вариант.
в библиотеке CEPHES есть хорошее решение, которое может быть довольно быстрым, и вы можете добавлять/удалять точность довольно гибко в течение немного большего/меньшего времени процессора.
помните, что cos(x) и sin(x) являются действительной и мнимой частями exp(ix). Поэтому мы хотим вычислить exp (ix), чтобы получить оба. Мы предварительно вычисляем exp (iy) для некоторых дискретных значений y между 0 и 2pi. Мы сдвигаем x на интервал [0, 2pi). Затем мы выбираем y, который ближе всего к x и пишем
exp (ix)=exp(iy+(ix-iy))=exp(iy) exp(i(x-y)).мы получаем exp (iy) из таблицы поиска. И поскольку |x-y| мал (не более половины расстояния между значениями y), ряд Тейлора будет хорошо сходиться всего за несколько терминов, поэтому мы используем это для exp(i(x-y)). И тогда нам просто нужно сложное умножение, чтобы получить exp(ix).
еще одним приятным свойством этого является то, что вы можете векторизовать его с помощью SSE.
вы можете взглянуть на http://gruntthepeon.free.fr/ssemath/, который предлагает векторизованную реализацию SSE, вдохновленную библиотекой CEPHES. Он имеет хорошую точность (максимальное отклонение от sin/cos на порядок 5e-8) и скорость (немного превосходит fsincos на основе одного вызова и явный победитель по нескольким значениям).
Я опубликовал решение, включающее встроенную сборку ARM, способную вычислять как синус, так и косинус двух углов одновременно здесь: быстрый синус / Косинус для ARMv7 + NEON
точное, но быстрое приближение функции sin и cos одновременно, в javascript, можно найти здесь:http://danisraelmalta.github.io/Fmath/ (легко импортируется в c / c++)