値のサインとコサインの両方を一緒に計算したい(たとえば、回転行列を作成する)。もちろん、a = cos(x); b = sin(x);
のように次々にそれらを個別に計算することもできますが、両方の値が必要なときにもっと速い方法があるのではないかと思います。
Edit:これまでの回答を要約するには:
Vlad は、両方を計算するasmコマンドFSINCOS
があると言いました(FSIN
の呼び出しとほぼ同時に)
Chi のように、この最適化はコンパイラによってすでに実行されている場合があります(最適化フラグを使用する場合)。
caf 指摘、関数sincos
およびsincosf
はおそらく利用可能であり、math.h
を含めるだけで直接呼び出すことができる
tanascius ルックアップテーブルを使用する方法については、議論の余地があります。 (ただし、私のコンピューターとベンチマークシナリオでは、sincos
の3倍の速度で実行され、32ビット浮動小数点とほぼ同じ精度です。)
Joel Goodwin 非常に正確な非常に高速な近似手法の興味深いアプローチにリンクされています(私にとっては、これはテーブル検索よりも高速です) )
最新の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/58847
それらのいずれかが役立つことを願っています。 (この命令は自分で使用しませんでした、ごめんなさい。)
プロセッサレベルでサポートされているため、テーブルルックアップよりもはるかに高速であると期待しています。
編集:
Wikipedia は、FSINCOS
が387プロセッサで追加されたことを示唆しているため、サポートしていないプロセッサはほとんど見つかりません。
編集:
Intelのドキュメント は、FSINCOS
がFDIV
(浮動小数点除算)の約5倍遅いと述べています。
編集:
最新のすべてのコンパイラーが、サインとコサインの計算をFSINCOS
の呼び出しに最適化するわけではないことに注意してください。特に、私のVS 2008はそれをしませんでした。
編集:
最初のリンク例は死んでいますが、 ウェイバックマシンにまだバージョンがあります があります。
最新の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命令を使用します!
技術的には、複素数と オイラーの式 を使用してこれを達成します。したがって、(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();
1ステップでサインとコサインが得られます。これが内部的にどのように行われるかは、使用されているコンパイラとライブラリの問題です。この方法でそれを行うには時間がかかる可能性があります(そして、オイラーの公式がexp
とsin
を使用して複雑なcos
を計算するためにほとんど使用されるので、逆ではありません)。
編集
GNU C++ 4.2の<complex>
のヘッダーはsin
内でcos
およびpolar
の明示的な計算を使用しているため、コンパイラーが何らかの魔法をかけない限り最適化にはあまり見かけません( チーの答え )で書かれている-ffast-math
および-mfpmath
スイッチを参照してください。
パフォーマンスが必要な場合は、事前に計算されたsin/cosテーブルを使用できます(1つのテーブルで実行でき、辞書として保存されます)。まあ、それはあなたが必要とする精度に依存します(テーブルが大きくなるかもしれません)が、それは本当に速いはずです。
どちらかを計算してから、IDを使用できます。
cos(x)2 = 1-sin(x)2
しかし、@ tanasciusが言うように、事前に計算されたテーブルを使用する方法です。
GNU Cライブラリを使用すると、次のことができます。
_#define _GNU_SOURCE
#include <math.h>
_
両方の値を一緒に計算するsincos()
、sincosf()
およびsincosl()
関数の宣言を取得します-おそらくターゲットアーキテクチャで最も高速な方法です。
このフォーラムページには非常に興味深いものがあり、高速で適切な近似値を見つけることに焦点を当てています。 http://www.devmaster.net/forums/showthread.php?t=5784
免責事項:このようなものは自分では使用していません。
2018年2月22日更新:元のページにアクセスするには、ウェイバックマシンが唯一の方法です: https://web.archive.org/web/20130927121234/http://devmaster.net/posts/9648/fast- and-accurate-sine-cosine
Cafが示すように、多くのC数学ライブラリにはすでにsincos()があります。注目すべき例外はMSVCです。
そして、ルックアップに関して、エリック・S・レイモンドはArt of Unix Programming(2004)(第12章)で、この悪い考え(現時点では)を明示的に述べています。
「別の例は、小さなテーブルの事前計算です。たとえば、3Dグラフィックエンジンで回転を最適化するためのsin(x)のテーブルは、現代のマシンでは365×4バイトを必要とします。これは明らかな速度の最適化でしたが、現在では、テーブルによるキャッシュミスの割合を支払うよりも、毎回再計算する方が速い場合があります。
「しかし、将来、キャッシュが大きくなると、これは再び変わる可能性があります。より一般的には、多くの最適化は一時的なものであり、コスト比率が変化すると悲観的になります。知る唯一の方法は測定して確認することです。」 (Art of Unix Programmingから)
しかし、上記の議論から判断すると、全員が同意するわけではありません。
ルックアップテーブルは、この問題に対して必ずしも良い考えだとは思いません。精度の要件が非常に低い場合を除き、テーブルは非常に大きくする必要があります。また、最新のCPUは、メインメモリから値を取得しながら多くの計算を実行できます。これは、引数(私のものでもない)で適切に回答できる質問の1つではなく、データをテストおよび測定し、検討するものです。
しかし、AMDのACMLやIntelのMKLなどのライブラリで見つかるSinCosの高速実装に注目します。
この記事では、サインとコサインの両方を生成する放物線アルゴリズムを構築する方法を示します。
DSPトリック:SinとCosの同時放物線近似
http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos
商用製品を使用する意思があり、同時に多数のsin/cos計算を計算する場合(ベクトル化された関数を使用できるように)、チェックアウトする必要があります IntelのMath Kernel Library。
そのドキュメントによると、コア2デュオの高精度モードでの平均は13.08クロック/要素であり、fsincosよりもさらに高速になると思います。
創造的なアプローチとして、Taylorシリーズを展開してみてはどうですか?似たような用語があるため、次のような擬似を行うことができます。
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とcosineで、パターンに従います-x ^ 2/2を引きます!コサインからx ^ 3/3を引きます!サインから、x ^ 4/4を追加します!コサインに、x ^ 5/5を追加します!サインする...
これがパフォーマンスになるかどうかはわかりません。組み込みのsin()およびcos()が提供する精度よりも低い精度が必要な場合は、オプションの可能性があります。
CEPHESライブラリにはニースのソリューションがあります。これは非常に高速であり、CPU時間を少し増やしたり減らしたりすることで、精度を非常に柔軟に追加/削除できます。
Cos(x)とsin(x)はexp(ix)の実部と虚部であることに注意してください。したがって、両方を取得するにはexp(ix)を計算します。 0から2piの間のyのいくつかの離散値に対してexp(iy)を事前計算します。 xを間隔[0、2pi)にシフトします。次に、xに最も近いyを選択して、
exp(ix)= exp(iy +(ix-iy))= exp(iy)exp(i(x-y))。
ルックアップテーブルからexp(iy)を取得します。そして| x-y |以来が小さい(最大でy値間の距離の半分)場合、テイラー級数はわずかな項でうまく収束するため、exp(i(x-y))に使用します。そして、exp(ix)を取得するには複雑な乗算が必要です。
これのもう1つの素晴らしい特性は、SSEを使用してベクトル化できることです。
http://gruntthepeon.free.fr/ssemath/ をご覧ください。CEPHESライブラリからインスピレーションを受けたSSEベクトル化された実装です。良好な精度(5e-8程度のsin/cosからの最大偏差)および速度(1回の呼び出しでfsincosをわずかに上回るパフォーマンス、および複数の値に対する明確な勝者)。
この種のことにパフォーマンスが重要な場合、ルックアップテーブルを導入することは珍しくありません。
JavaScriptでのsin関数とcos関数の正確かつ高速な近似は、ここで見つけることができます: http://danisraelmalta.github.io/Fmath/ (c/c ++に簡単にインポート)
インラインを含むソリューションを投稿しましたARM一度に2つの角度のサインとコサインの両方を計算できるアセンブリ: ARMv7 + NEONの高速サイン/コサイン
MSVCコンパイラーは(内部)SSE2関数を使用する場合があります
___libm_sse2_sincos_ (for x86)
__libm_sse2_sincos_ (for x64)
最適化されたビルドでは、適切なコンパイラフラグが指定されている場合(少なくとも/ O2/Arch:SSE2/fp:fast)。これらの関数の名前は、別々のsinとcosを計算するのではなく、どちらも「1ステップ」で計算することを意味するようです。
例えば:
void sincos(double const x, double & s, double & c)
{
s = std::sin(x);
c = std::cos(x);
}
/ fp:fastを使用したアセンブリ(x86の場合):
movsd xmm0, QWORD PTR _x$[esp-4]
call ___libm_sse2_sincos_
mov eax, DWORD PTR _s$[esp-4]
movsd QWORD PTR [eax], xmm0
mov eax, DWORD PTR _c$[esp-4]
shufpd xmm0, xmm0, 1
movsd QWORD PTR [eax], xmm0
ret 0
/ fp:fastを使用せず、代わりに/ fp:preciseを使用する(デフォルト)アセンブリ(x86の場合)は、sinとcosを別々に呼び出します。
movsd xmm0, QWORD PTR _x$[esp-4]
call __libm_sse2_sin_precise
mov eax, DWORD PTR _s$[esp-4]
movsd QWORD PTR [eax], xmm0
movsd xmm0, QWORD PTR _x$[esp-4]
call __libm_sse2_cos_precise
mov eax, DWORD PTR _c$[esp-4]
movsd QWORD PTR [eax], xmm0
ret 0
したがって、sincosの最適化には/ fp:fastが必須です。
ただし、ご注意ください
___libm_sse2_sincos_
おそらくそれほど正確ではない
__libm_sse2_sin_precise
__libm_sse2_cos_precise
名前の末尾に「正確」が欠落しているためです。
最新のMSVC 2019コンパイラと適切な最適化を備えた「わずかに」古いシステム(Intel Core 2 Duo E6750)では、sincos呼び出しは、sinとcosを別々に呼び出した場合よりも約2.4倍速いことがベンチマークで示されています。
2つの関数のルックアップテーブルを宣言することを考えましたか?それでもsin(x)とcos(x)を「計算」する必要がありますが、高度な精度が必要なければ、明らかに高速になります。