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


100

値の正弦と余弦の両方を一緒に計算したい(たとえば、回転行列を作成したい)。もちろん、のように次々に別々に計算することもできますa = cos(x); b = sin(x);が、両方の値が必要な場合により速い方法があるかどうか疑問に思います。

編集: これまでの答えを要約するには:

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

  • Chiが気づいたように、この最適化はコンパイラーによって既に行われていることがあります(最適化フラグを使用する場合)。

  • カフェは機能していること、を指摘sincosしてはsincosf、おそらく利用可能であり、ちょうど含めることによって直接呼び出すことができますmath.h

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

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


1
sin / cosのネイティブ実装に関するこの質問も参照してください:stackoverflow.com/questions/1640595
Joel Goodwin

1
しようとsinx ~ x-x^3/6してcosx~1-x^2/4近似値として、あなたは正確さよりも、もっとスピードを気にしている場合。正確さを重視する場合は、どちらのシリーズでも用語を追加できます(en.wikipedia.org/wiki/Taylor_seriesスクロールしてテイラーシリーズをトリガーします)。これは、微分可能な時間である必要な関数を概算する一般的な方法ですn。したがって、sinとcosが属しているいくつかのより大きな関数がある場合、sin、cosを独立して近似するのではなく、近似すると、速度が大幅に向上します。
ldog 2010

これは、精度が非常に低い貧弱なテクニックです。Joel Goodwinによる投稿を参照してください。テイラーシリーズは以下に掲載されています。回答として投稿してください。
Danvil

1
まあそれはあなたの要件に依存します。もし正確さが欲しいなら、ある点に近い値が必要な場合にのみテイラー級数が良い近似になります。そして、テイラー級数を0の代わりに拡大します。これは近くで優れた精度を与えます結果が悪化します。与えられた回答を見て、から離れた値で試してみたところ、精度が原因と考えられます。その答えは罪であり、COSは約0拡大xx_0x_0x_00
ldog

回答:


52

最新の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

それらの1つが役立つことを願っています。(申し訳ありませんが、私はこの説明を自分で使用していません。)

それらはプロセッサレベルでサポートされているため、テーブルルックアップよりもはるかに高速であることが期待されます。

編集:
ウィキペディアは、FSINCOS387プロセッサで追加されたことを示唆しているため、それをサポートしていないプロセッサはほとんど見つかりません。

編集:
Intelのドキュメントには、浮動小数点除算FSINCOSよりも約5倍遅いと記載されていFDIVます。

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

編集:
最初のサンプルリンクは無効ですが、Wayback Machineにはまだバージョンがあります。


1
@phkahler:いいですね。このような最適化が最新のコンパイラーで使用されているかどうかはわかりません。
Vlad

12
fsincos命令はありません、「かなり速いです」。Intel独自の最適化マニュアルでは、最近のマイクロアーキテクチャでは119〜250サイクルが必要であるとしています。比較すると、インテルの数学ライブラリー(ICCと共に配布)は、個別に計算しsincos代わりのx87ユニットのSSEを使用するソフトウェアの実装を使用して、100サイクル未満で。両方を同時に計算した同様のソフトウェア実装は、さらに高速になる可能性があります。
スティーブンキャノン

2
@Vlad:ICC数学ライブラリはオープンソースではなく、それらを再配布するライセンスがないため、アセンブリを投稿できません。内蔵されていないことがわかりますsinただし、それらを利用するための計算。彼らは他の人と同じSSE命令を使用します。2番目のコメントに対して、相対的fdivな速度は重要ではありません。何かを行う2つの方法があり、一方が他方の2倍の速さである場合、完全に無関係なタスクと比較してどれだけ時間がかかるかに関係なく、遅い方を「高速」と呼んでも意味がありません。
スティーブンキャノン

1
sinライブラリのソフトウェア関数は、完全な倍精度の精度を提供します。このfsincos命令は多少精度が高くなりますが(ダブル拡張)、sinその結果は通常、関数を呼び出すほとんどのプログラムで破棄されます。その結果は通常、後の算術演算またはメモリへのストアによって倍精度に丸められるためです。ほとんどの場合、実際に使用する場合と同じ精度が得られます。
スティーブンキャノン

4
また、それだけでfsincosは完全な実装ではないことに注意してください。引数をfsincos命令の有効な入力範囲に入れるには、追加の範囲縮小ステップが必要です。ライブラリsincos関数には、この削減とコア計算が含まれているため、リストしたサイクルタイミングが示すよりも(比較すると)さらに高速です。
スティーブン・キャノン

39

現代の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命令を使っています!


これはカッコいい!-mfpmath = 387が何をしているのか説明できますか?また、MSVCでも動作しますか?
Danvil

1
なお、-ffast-math及び-mfpmathいくつかのケースでは異なる結果につながります。
Debilski、2010

3
mfpmath = 387は、gccがSSE命令の代わりにx87命令を使用するように強制します。MSVCにも同様の最適化とフラグがあると思いますが、確実なMSVCがありません。x87命令を使用すると、他のコードのパフォーマンスが低下する可能性がありますが、IntelのMKLを使用するには、他の回答も参照する必要があります。
カイ

cygwinからの私の古いgcc 3.4.4は、fsinおよびへの2つの別々の呼び出しを生成しますfcos。:-(
Vlad

最高の最適化を有効にしてVisual Studio 2008で試してみました。2つのライブラリ関数__CIsinとを呼び出します__CIcos
ウラッド、

13

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


次に、入力値を[0,2 * pi](または追加のチェックでより小さく)にマップする必要があり、このfmodの呼び出しはパフォーマンスを損ないます。私の(おそらく次善の)実装では、ルックアップテーブルでパフォーマンスを得ることができませんでした。ここで何かアドバイスはありますか?
Danvil

11
事前計算されたテーブルはsinキャッシュを破壊するため、事前計算されたテーブルは呼び出しよりも遅くなる可能性がほとんどです。
Andreas Brinck 2010

1
テーブルの大きさによって異なります。多くの場合、256エントリのテーブルは非常に正確で、1 KBしか使用しません...それをたくさん使用すると、アプリの残りのパフォーマンスに悪影響を与えることなく、キャッシュにスタックしませんか?
ボーイ氏

@ダンビル:これは、サインルックアップテーブルen.wikipedia.org/wiki/Lookup_table#Computing_sinesの例です。ただし、入力も[0; 2pi]にマッピング済みであると想定しています。
tanascius 2010

@AndreasBrinck私はそんなに遠くに行きません。それはDepends(TM)です。最新のキャッシュは巨大で、ルックアップテーブルは小さいです。メモリレイアウトに少し注意を払えば、ルックアップテーブルは残りの計算のキャッシュ使用率に影響を与える必要はほとんどありません。ルックアップテーブルがキャッシュ内に収まるという事実は、キャッシュが非常に高速である理由の1つです。メモリレイアウトを正確に制御するのが難しいJavaでも、ルックアップテーブルを使用するとパフォーマンスが大幅に向上しました。
ジャロッド・スミス

13

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


編集する

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


申し訳ありませんが、オイラーの公式では実際の計算方法は示されていません。複素数の指数を実際の三角関数に関連付けるのは(非常に有用なものですが)単なる同一性です。サインとコサインを一緒に計算することには利点がありますが、それらには共通の部分式が含まれるため、答えはこれについては説明しません。
Jason S

12

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

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

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


8
また、この方法の使用には指数と平方根の計算が含まれるため、パフォーマンスが重要な場合は、これが他のトリガー関数を直接計算するよりも実際に速いことを確認してください。
タイラーマクヘンリー

4
sqrt()多くの場合、ハードウェアで最適化されているため、sin()またはと比べて非常に高速cos()です。パワーは自己乗算なので、を使用しないでくださいpow()。ハードウェアサポートなしでかなり正確な平方根を非常に迅速に取得するためのいくつかのトリックがあります。最後に、これを行う前に必ずプロファイルを作成してください。
deft_code

12
注√こと(1 - COS ^ 2 X)は、特に、罪を直接xは計算よりも正確である場合、X〜0
kennytm

1
小さなxの場合、y = sqrt(1-x * x)のテイラー級数は非常に優れています。最初の3項で精度を上げることができ、数回の乗算と1回のシフトのみが必要です。固定小数点コードで使用しました。
phkahler 2010

1
@phkahler:理由は、X〜0、COS X〜1.あなたのテイラーシリーズは適用されません
kennytm

10

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

#define _GNU_SOURCE
#include <math.h>

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


8

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


私もこれを試してみましたが、かなり良いパフォーマンスが得られました。ただし、sinとcosは独立して計算されます。
Danvil

私の考えでは、この正弦/余弦の計算は、正弦を取得して平方根近似を使用して余弦を取得するよりも高速ですが、テストでそれを確認できます。サインとコサインの主な関係はフェーズの1つです。これを考慮して、位相シフトされたコサイン呼び出しに対して計算したサイン値を再利用できるようにコーディングすることは可能ですか?(これは一続きになるかもしれませんが、尋ねなければなりませんでした)
Joel Goodwin

直接ではありません(これを正確に尋ねる質問にもかかわらず)。値xのsinとcosが必要です。他の場所でx + pi / 2を偶然計算したかどうかを知る方法はありません...
Danvil

ゲームでこれを使用して、パーティクルの円を描きました。それは単なる視覚効果なので、結果は十分に近く、パフォーマンスは本当に印象的です。
Maxim Kamalov、2015年

あまり面白くありませんでした; チェビシェフ近似は、通常、特定のパフォーマンスで最も正確です。
Jason S

7

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

  • Sunは少なくとも1987年から(23年目、私はハードコピーのmanページを持っています)sincos()を使用しています。
  • HPUX 11には1997年に搭載されていました(ただしHPUX 10.20には搭載されていません)
  • バージョン2.1でglibcに追加(1999年2月)
  • gcc 3.4(2004)、__ builtin_sincos()の組み込みになりました。

ルックアップに関しては、Unixプログラミング芸術(2004)のエリックS.レイモンド(第12章)は、これは悪い考えだと明言しています(現時点では)。

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

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

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


10
「365 x 4バイト」。うるう年を考慮する必要があるため、実際には365.25 x 4バイトになるはずです。あるいは、地球の年の日数ではなく、円の度数を使用するつもりでした。
ポンカドゥードル

@Wallacoloo:素晴らしい観察。見逃した。しかし、エラーは元にあります。
ジョセフクインジー

笑。さらに、その地域の多くのコンピュータゲームでは、有限数の角度しか必要ないという事実無視しています。可能な角度を知っていれば、キャッシュミスはありません。この場合はテーブルを正確に使用し、fsincos(CPU命令!)他のものを試してみます。多くの場合、大きなテーブルからsinとcosを補間するのと同じくらい高速です。
Erich Schubert

5

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

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


3

商用製品を使用する意思があり、同時に多数のsin / cos計算を計算している場合(ベクトル化された関数を使用できるようにする場合)は、IntelのMath Kernel Libraryを確認してください

それは sincos関数を

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


1
同様に、OSX上の一つが使用できvvsincos又はvvsincosfAccelerate.frameworkから。AMDのベクターライブラリにも同様の機能があると思います。
スティーブンキャノン


2

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


2

クリエイティブなアプローチとして、テイラーシリーズを拡張してみませんか?それらは類似した用語を持っているので、次の疑似のようなことをすることができます:

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
}

これは、あなたがこのようなことをすることを意味します:sinとcosineのxと1から始めて、パターンに従います-x ^ 2/2を引きます!コサインから、x ^ 3/3を引きます!サインから、x ^ 4/4を追加!コサインにx ^ 5/5を追加!サインする...

これが効果的かどうかはわかりません。組み込みのsin()およびcos()が提供するよりも低い精度が必要な場合は、それがオプションである可能性があります。


実際には、i-サイン拡張係数はx / i倍のi-コサイン拡張係数です。しかし、テイラーシリーズの使用が本当に速いのではないかと思います...
Danvil

1
多項式関数の近似については、チェビシェフはテイラーよりもはるかに優れています。テイラー近似を使用しないでください。
Timmmm 2017年

ここにはたくさんの数の偽のpasがあります。分子と分母の両方が急速に大きくなり、浮動小数点エラーが発生します。「精度が十分でない」とはどのように決定し、どのように計算するかは言うまでもありません。テイラー近似は、単一の点の周辺で適切です。その点から離れると、それらはすぐに不正確になり、多数の項を必要とするため、チェビシェフの近似に関するTimmmmの提案(指定された間隔で適切な近似を作成する)は適切なものです。
Jason S

2

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(xy))ます。

ルックアップテーブルからexp(iy)を取得します。そして| xy |以来 が小さい(最大でy値間の距離の半分)場合、テイラー級数は数項でうまく収束するため、exp(i(xy))に使用します。そして、exp(ix)を取得するには、複雑な乗算が必要です。

これのもう1つの優れた特性は、SSEを使用してベクトル化できることです。


2

あなたは見ていたいかもしれhttp://gruntthepeon.free.fr/ssemath/ CEPHESライブラリからインスピレーションを得たSSEベクトル化の実装を提供しています、。精度(sin / cosからの最大偏差が5e-8である)と速度(1回の呼び出しではfsincosをわずかに上回っており、複数の値で明らかに勝者です)。




0

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


0

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.