Я хотел бы вычислить и синус и косинус значения вместе (например, для создания матрицы вращения). Конечно, я мог вычислить их отдельно один за другим как a = cos(x); b = sin(x);
, но интересно, существует ли более быстрый путь при необходимости в обоих значениях.
Править: Суммировать ответы до сих пор:
Vlad сказал, что существует команда asm FSINCOS
вычисления их обоих (в почти то же время как вызов к FSIN
один)
Как замеченный Chi, эта оптимизация иногда уже делается компилятором (при использовании флагов оптимизации).
caf указал, который функционирует sincos
и sincosf
вероятно, доступны и может быть назван непосредственно только включая math.h
подход tanascius использования справочной таблицы обсужден спорный. (Однако на моем компьютере и в сценарии сравнительного теста это работает 3x быстрее, чем sincos
почти с той же точностью для 32-разрядных плавающих точек.)
Joel Goodwin связался с интересным подходом чрезвычайно быстрого метода приближенных вычислений с довольно хорошим accuray (для меня, это еще быстрее затем поиск по таблице),
Современные процессоры 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
Надеюсь, что один из них помогает. (Я сам не использовал эту инструкцию, извините.)
Поскольку они поддерживаются на уровне процессора, я ожидаю, что они будут намного быстрее, чем поиск по таблицам.
Редактировать:
Википедия предполагает, что FSINCOS
был добавлен на 387 процессорах, поэтому вы вряд ли найдете процессор, который его не поддерживает.
Правка:
В документации Intel указано, что FSINCOS
примерно в 5 раз медленнее, чем FDIV
(т.е. деление с плавающей запятой).
Изменить:
Обратите внимание, что не все современные компиляторы оптимизируют вычисление синуса и косинуса при вызове FSINCOS
. В частности, мой VS 2008 этого не делал.
Редактировать:
Первая ссылка в качестве примера не работает, но есть версия на Wayback Machine .
Думали ли вы об объявлении таблиц поиска для двух функций? Вам все равно придется «вычислять» sin (x) и cos (x), но это будет значительно быстрее, если вам не нужна высокая степень точности.
На этой странице форума есть очень интересные материалы, направленные на быстрый и быстрый поиск хороших приближений: http://www.devmaster.net/forums/showthread.php?t= 5784
Отказ от ответственности: Сам не использовал эти материалы.
Обновление от 22 февраля 2018 г .: Wayback Machine - единственный способ перейти на исходную страницу сейчас: https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/ быстрый и точный синус-косинус
Что касается творческого подхода, как насчет расширения серии Тейлора? Поскольку у них схожие термины, вы можете сделать что-то вроде следующего псевдо:
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 для sin и косинуса, следуйте шаблону - вычтите x ^ 2/2! из косинуса вычтите x ^ 3/3! из синуса прибавить x ^ 4/4! к косинусу прибавить x ^ 5/5! to sine ...
Я понятия не имею, будет ли это производительным. Если вам нужна меньшая точность, чем дают встроенные функции sin () и cos (), это может быть вариантом.
Я не верю, что справочные таблицы обязательно являются хорошей идеей для решения этой проблемы. Если ваши требования к точности не очень низкие, таблица должна быть очень большой. А современные процессоры могут выполнять много вычислений, пока значение извлекается из основной памяти. Это не один из тех вопросов, на которые можно правильно ответить с помощью аргументов (даже не моих), тестирования, измерения и рассмотрения данных.
Но я бы посмотрел на быстрые реализации SinCos, которые вы найдете в таких библиотеках, как AMD ACML и Intel MKL.
Когда производительность критична для такого рода вещей, нет ничего необычного во введении таблицы поиска.
Современные процессоры 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 $4
.subsections_via_symbols
Тада, он использует инструкцию fsincos!
Если вы используете библиотеку GNU C, вы можете:
#define _GNU_SOURCE
#include <math.h>
и получите объявления sincos ()
, sincosf ()
и sincosl ()
функции, которые вычисляют оба значения вместе - вероятно, самым быстрым способом для вашей целевой архитектуры.
Вы можете вычислить любое из них, а затем использовать идентификатор:
cos(x)2 = 1 - sin(x)2
, но, как говорит @tanascius, заранее вычисленная таблица - это лучший вариант.
Технически этого можно добиться, используя комплексные числа и формулу Эйлера . Таким образом, что-то вроде (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
- а не наоборот), но возможна теоретическая оптимизация.
Править
Заголовки в
для GNU C ++ 4.2 используют явные вычисления sin
и cos
внутри полярных
], так что там он не будет выглядеть слишком хорошо для оптимизации, если компилятор не произведет магию (см. переключатели -ffast-math
и -mfpmath
, как написано в Ответ Чи ).
Когда вам нужна производительность, вы можете использовать предварительно вычисленную таблицу sin / cos (подойдет одна таблица, сохраненная в виде словаря). Ну, это зависит от необходимой точности (возможно, таблица будет слишком большой), но это должно быть очень быстро.
Если вы хотите использовать коммерческий продукт и одновременно рассчитываете несколько вычислений sin / cos (чтобы вы могли использовать векторные функции), вам следует обратиться к библиотеке Intel Math Kernel Library.
Он имеет функцию sincos
Согласно этой документации, он в среднем составляет 13,08 тактов на элемент в дуэте ядра 2 в режиме высокой точности, что, я думаю, будет даже быстрее, чем fsincos.
Многие математические библиотеки C, как указывает caf, уже имеют sincos (). Заметным исключением является MSVC.
Что касается поиска, Эрик С. Реймонд в Art of Unix Programming (2004) (глава 12) прямо говорит, что это плохая идея (в настоящий момент):
«Другой пример - это предварительное вычисление небольших таблиц - например, таблица sin (x) по градусам для оптимизации вращений в движке трехмерной графики будет занимать 365 × 4 байта на современной машине. До того, как процессоры стали достаточно быстрее памяти, чтобы требовать кэширования, это была очевидная оптимизация скорости . В настоящее время может быть быстрее каждый раз пересчитывать , чем платить за процент дополнительных промахи в кэше, вызванные таблицей .
"Но в будущем это может измениться снова, когда кеш станет больше. В целом, многие оптимизации являются временными и могут легко изменить {{1} }} в пессимизацию по мере изменения соотношений затрат. Единственный способ узнать это - измерить и увидеть ". (Из Искусство программирования Unix )
Но, судя по обсуждению выше, не все согласны.
{ {1}}Я опубликовал решение, включающее встроенную сборку ARM, способную вычислять как синус, так и косинус двух углов одновременно: Быстрый синус / косинус для ARMv7 + NEON
Эта статья показывает, как построить параболический алгоритм, который генерирует и синус, и косинус:
DSP Trick: Simultaneous Parabolic Approximation of Sin and Cos
http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos