Каков самый быстрый способ вычислить грех и cos вместе?


Я хотел бы вычислить как синус, так и ко-синус значения вместе (например, для создания матрицы вращения). Конечно, я мог бы вычислить их отдельно друг за другом, как a = cos(x); b = sin(x);, но мне интересно, есть ли более быстрый способ, когда нужны оба значения.

Edit: Обобщить ответы до сих пор:

  • Влад сказал, что есть команда asm FSINCOS вычисление обоих из них (почти в то же время, что и a звоните в )

  • как Чи замечено, что эта оптимизация иногда уже выполняется компилятором (при использовании флагов оптимизации).

  • caf указано, что функции sincos и sincosf вероятно, доступны и могут быть вызваны непосредственно, просто включая math.h

  • tanascius подход использования таблицы поиска обсуждается спорным. (Однако на моем компьютере и в тестовом сценарии он работает в 3 раза быстрее, чем sincos С почти такой же точностью 32-битной плавающей точки.)

  • Джоэл Гудвин связано с интересным подходом чрезвычайно быстрой техники аппроксимации с довольно хорошей точностью (для меня это еще быстрее, чем поиск таблицы)

18 92

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++)

вы думали об объявлении таблиц поиска для двух функций? Вам все равно придется "вычислить" sin(x) и cos(x), но это будет решительно быстрее, если вам не нужна высокая степень точности.