web-dev-qa-db-ja.com

sinとcosを一緒に計算する最速の方法は何ですか?

値のサインとコサインの両方を一緒に計算したい(たとえば、回転行列を作成する)。もちろん、a = cos(x); b = sin(x);のように次々にそれらを個別に計算することもできますが、両方の値が必要なときにもっと速い方法があるのではないかと思います。

Edit:これまでの回答を要約するには:

  • Vlad は、両方を計算するasmコマンドFSINCOSがあると言いました(FSINの呼び出しとほぼ同時に)

  • Chi のように、この最適化はコンパイラによってすでに実行されている場合があります(最適化フラグを使用する場合)。

  • caf 指摘、関数sincosおよびsincosfはおそらく利用可能であり、math.hを含めるだけで直接呼び出すことができる

  • tanascius ルックアップテーブルを使用する方法については、議論の余地があります。 (ただし、私のコンピューターとベンチマークシナリオでは、sincosの3倍の速度で実行され、32ビット浮動小数点とほぼ同じ精度です。)

  • Joel Goodwin 非常に正確な非常に高速な近似手法の興味深いアプローチにリンクされています(私にとっては、これはテーブル検索よりも高速です) )

98
Danvil

最新の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のドキュメント は、FSINCOSFDIV(浮動小数点除算)の約5倍遅いと述べています。

編集:
最新のすべてのコンパイラーが、サインとコサインの計算をFSINCOSの呼び出しに最適化するわけではないことに注意してください。特に、私のVS 2008はそれをしませんでした。

編集:
最初のリンク例は死んでいますが、 ウェイバックマシンにまだバージョンがあります があります。

51
Vlad

最新の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命令を使用します!

38
Chi

技術的には、複素数と オイラーの式 を使用してこれを達成します。したがって、(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ステップでサインとコサインが得られます。これが内部的にどのように行われるかは、使用されているコンパイラとライブラリの問題です。この方法でそれを行うには時間がかかる可能性があります(そして、オイラーの公式がexpsinを使用して複雑なcosを計算するためにほとんど使用されるので、逆ではありません)。


編集

GNU C++ 4.2の<complex>のヘッダーはsin内でcosおよびpolarの明示的な計算を使用しているため、コンパイラーが何らかの魔法をかけない限り最適化にはあまり見かけません( チーの答え )で書かれている-ffast-mathおよび-mfpmathスイッチを参照してください。

13
Debilski

パフォーマンスが必要な場合は、事前に計算されたsin/cosテーブルを使用できます(1つのテーブルで実行でき、辞書として保存されます)。まあ、それはあなたが必要とする精度に依存します(テーブルが大きくなるかもしれません)が、それは本当に速いはずです。

13
tanascius

どちらかを計算してから、IDを使用できます。

cos(x)2 = 1-sin(x)2

しかし、@ tanasciusが言うように、事前に計算されたテーブルを使用する方法です。

12
Mitch Wheat

GNU Cライブラリを使用すると、次のことができます。

_#define _GNU_SOURCE
#include <math.h>
_

両方の値を一緒に計算するsincos()sincosf()およびsincosl()関数の宣言を取得します-おそらくターゲットアーキテクチャで最も高速な方法です。

8
caf

このフォーラムページには非常に興味深いものがあり、高速で適切な近似値を見つけることに焦点を当てています。 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

8
Joel Goodwin

Cafが示すように、多くのC数学ライブラリにはすでにsincos()があります。注目すべき例外はMSVCです。

  • Sunには少なくとも1987年からsincos()があります(23年。私はハードコピーのマニュアルページを持っています)
  • HPUX 11には1997年にありました(しかしHPUX 10.20にはありません)
  • バージョン2.1でglibcに追加(1999年2月)
  • Gcc 3.4(2004)の組み込み、__ builtin_sincos()になりました。

そして、ルックアップに関して、エリック・S・レイモンドはArt of Unix Programming(2004)(第12章)で、この悪い考え(現時点では)を明示的に述べています。

「別の例は、小さなテーブルの事前計算です。たとえば、3Dグラフィックエンジンで回転を最適化するためのsin(x)のテーブルは、現代のマシンでは365×4バイトを必要とします。これは明らかな速度の最適化でしたが、現在では、テーブルによるキャッシュミスの割合を支払うよりも、毎回再計算する方が速い場合があります。

「しかし、将来、キャッシュが大きくなると、これは再び変わる可能性があります。より一般的には、多くの最適化は一時的なものであり、コスト比率が変化すると悲観的になります。知る唯一の方法は測定して確認することです。」 (Art of Unix Programmingから)

しかし、上記の議論から判断すると、全員が同意するわけではありません。

7
Joseph Quinsey

ルックアップテーブルは、この問題に対して必ずしも良い考えだとは思いません。精度の要件が非常に低い場合を除き、テーブルは非常に大きくする必要があります。また、最新のCPUは、メインメモリから値を取得しながら多くの計算を実行できます。これは、引数(私のものでもない)で適切に回答できる質問の1つではなく、データをテストおよび測定し、検討するものです。

しかし、AMDのACMLやIntelのMKLなどのライブラリで見つかるSinCosの高速実装に注目します。

この記事では、サインとコサインの両方を生成する放物線アルゴリズムを構築する方法を示します。

DSPトリック:SinとCosの同時放物線近似

http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos

3
Probes

商用製品を使用する意思があり、同時に多数のsin/cos計算を計算する場合(ベクトル化された関数を使用できるように)、チェックアウトする必要があります IntelのMath Kernel Library。

sincos関数

そのドキュメントによると、コア2デュオの高精度モードでの平均は13.08クロック/要素であり、fsincosよりもさらに高速になると思います。

3
Chi

創造的なアプローチとして、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()が提供する精度よりも低い精度が必要な場合は、オプションの可能性があります。

2
Tesserex

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を使用してベクトル化できることです。

2
Jsl

http://gruntthepeon.free.fr/ssemath/ をご覧ください。CEPHESライブラリからインスピレーションを受けたSSEベクトル化された実装です。良好な精度(5e-8程度のsin/cosからの最大偏差)および速度(1回の呼び出しでfsincosをわずかに上回るパフォーマンス、および複数の値に対する明確な勝者)。

2
SleuthEye

この種のことにパフォーマンスが重要な場合、ルックアップテーブルを導入することは珍しくありません。

2
Tom Cabanski

JavaScriptでのsin関数とcos関数の正確かつ高速な近似は、ここで見つけることができます: http://danisraelmalta.github.io/Fmath/ (c/c ++に簡単にインポート)

1
user2781980

インラインを含むソリューションを投稿しましたARM一度に2つの角度のサインとコサインの両方を計算できるアセンブリ: ARMv7 + NEONの高速サイン/コサイン

1
jcayzac

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倍速いことがベンチマークで示されています。

0
x y

2つの関数のルックアップテーブルを宣言することを考えましたか?それでもsin(x)とcos(x)を「計算」する必要がありますが、高度な精度が必要なければ、明らかに高速になります。

0
Frank Shearar