web-dev-qa-db-ja.com

C ++用の三角関数の高速実装

ショートバージョン:math.hに含まれているものよりも高速な標準三角関数の実装があるかどうかを知りたいです。

長いバージョン:数値が非常に重いプログラム(物理シミュレーションです)で、三角関数を呼び出す必要があるプログラム(ほとんどはsincos)が必要です。現在私は単にmath.hに含まれる実装を使用しています。プロファイリングは、これらの関数の呼び出しのコストが期待していたよりも高いことを示しています(期待しています)。

コードの他の部分には最適化の余地が十分あることは間違いありませんが、sincosを高速化すると、さらに1パーセント多くなる可能性があります。
別の post では、自作のルックアップテーブルの使用が提案されています。しかし、代替案はありますか?または、一部のライブラリの既製で十分にテストされたルックアップソリューションですか?

33
janitor048

以下は、トリガー関数のべき級数近似(ただし、テイラー級数ではない)を行う方法に関する優れたスライドです。 Faster Math Functions

これは、ゲームプログラマー向けです。つまり、パフォーマンスのために精度が犠牲になりますが、近似に別の1つまたは2つの項を追加して、精度の一部を取り戻すことができるはずです。

これのいいところは、SIMDに簡単に拡張できるようにして、4つの値のsinまたはcosを一度に計算できるようにすることです(倍精度を使用している場合は2)。

お役に立てば幸い...

17
celion

これをさらに最適化できる場合は、これはかなり速くなるはずです。コードを実行して、pastie.orgなどに投稿してください。

コンピュータの仕様-> 512MB Ram、Visual Studio 2010、Windows XP Professional SP3 Version 2002、Intel(R)Pentium(R)4 CPU 2.8GHZ。

これはめちゃくちゃ正確で、実際には状況によっては少し良い結果が得られます。例えば。 C++での90、180、270度は、0以外の10進数を返します。

0から359度の完全な表: https://pastee.org/dhwbj

FORMAT-> DEGREE#-> MINE_X(#)、CosX(#)、MINE_Z(#)、SinZ(#)。

以下は、上記の表を作成するために使用されるコードです。より大きなデータ型を使用する場合は、おそらくさらに正確にすることができます。私は無署名のショートを利用し、N/64000を行いました。それで、最も近いcos(##)とsin(##)は、そのインデックスに丸められます。 cosとsinに720のfloat値を使用した乱雑なテーブルにならないように、できる限り少ない追加のデータを使用することも試みました。これはおそらくより良い結果をもたらしますが、メモリの完全な無駄になります。下の表は私ができる限り小さいです。これらすべての短い値に丸めることができる方程式を作成し、代わりにそれを使用できるかどうかを確認したいと思います。速くなるかどうかはわかりませんが、テーブルが完全に削除され、おそらく速度が低下することはありません。

したがって、C++ cos/sin操作と比較した場合の精度は99.99998%から100%です。

以下は、cos/sin値の計算に使用されるテーブルです。

static const unsigned __int16 DEGREE_LOOKUP_TABLE[91] =
{
    64000, 63990, 63961, 63912, 63844, 63756,
    63649, 63523, 63377, 63212, 63028, 62824,
    62601, 62360, 62099, 61819, 61521, 61204,
    60868, 60513, 60140, 59749, 59340, 58912,
    58467, 58004, 57523, 57024, 56509, 55976,
    55426, 54859, 54275, 53675, 53058, 52426,
    51777, 51113, 50433, 49737, 49027, 48301,
    47561, 46807, 46038, 45255, 44458, 43648,
    42824, 41988, 41138, 40277, 39402, 38516,
    37618, 36709, 35788, 34857, 33915, 32962,
    32000, 31028, 30046, 29055, 28056, 27048,
    26031, 25007, 23975, 22936, 21889, 20836,
    19777, 18712, 17641, 16564, 15483, 14397,
    13306, 12212, 11113, 10012,  8907,  7800,
     6690,  5578,  4464,  3350,  2234,  1117,
        0,
};

以下は、cos/sin計算を行う実際のコードです。

    int deg1 = (int)degrees;
    int deg2 = 90 - deg1;
    float module = degrees - deg1;
    double vX = DEGREE_LOOKUP_TABLE[deg1] * 0.000015625;
    double vZ = DEGREE_LOOKUP_TABLE[deg2] * 0.000015625;
    double mX = DEGREE_LOOKUP_TABLE[deg1 + 1] * 0.000015625;
    double mZ = DEGREE_LOOKUP_TABLE[deg2 - 1] * 0.000015625;
    float vectorX = vX + (mX - vX) * module;
    float vectorZ = vZ + (mZ - vZ) * module;
    if (quadrant & 1)
    {
        float tmp = vectorX;
        if (quadrant == 1)
        {
            vectorX = -vectorZ;
            vectorZ = tmp;
        } else {
            vectorX = vectorZ;
            vectorZ = -tmp;
        }
    } else if (quadrant == 2) {
        vectorX = -vectorX;
        vectorZ = -vectorZ;
    }

もともと言及したコンピュータの仕様を使用して以下のスピード。これがデバッグモードになる前にデバッグモードで実行していましたが、デバッグなしでデバッグできると思われる実行可能ファイルを実行しています。

私の方法

1,000 Iterations -> 0.004641 MS or 4641 NanoSeconds.
100,000 Iterations -> 4.4328 MS.
100,000,000 Iterations -> 454.079 MS.
1,000,000,000 Iterations -> 4065.19 MS.

COS/SINメソッド

1,000 Iterations -> 0.581016 MS or 581016 NanoSeconds.
100,000 Iterations -> 25.0049 MS.
100,000,000 Iterations -> 24,731.6 MS.
1,000,000,000 Iterations -> 246,096 MS.

上記を要約すると、私の戦略でcos(###)とsin(###)の両方を実行すると、毎秒約220,000,000の実行が可能になります。もともと示されたコンピュータの仕様を利用しています。これはかなり高速で、メモリをほとんど使用しないので、C++で通常見られる数学cos/sin関数の優れた代替品です。精度を確認したい場合は、上記のリンクを開くと、359から0度までの印刷が表示されます。また、これは0から89および象限0から3をサポートします。したがって、それを使用するか、または( DEGREES%90)。

7
Jeremy Trifilo

カスタム実装を使用する場合は、 herehere および here を参照してください

また、大きな配列のsin/cosを計算する必要がある場合は here (Universal SIMD-Mathlibraryにスクロール)

C++を使用することもできますSSE組み込み関数。見てください ここ

最新のコンパイラのほとんどはSSEおよびSSE2の最適化をサポートしています。たとえば、Visual Studio 2010の場合、手動で有効にする必要があります。これを実行すると、ほとんどの場合、異なる実装が使用されます。標準の数学関数。

もう1つのオプションは、DirectX HLSLを使用することです。 こちら を見てください。 sinとcosの両方を返すNice sincos 関数があることに注意してください。

通常、IPPを使用します(これは無料ではありません)。詳細はこちら こちら

3
Lior Kogan

Quake 3のソースには、精度より速度を目的とした事前計算された正弦/余弦のコードがあり、sseベースではないため、非常に移植可能です(アーキテクチャと組み込みAPIの両方)。このsseおよびsse2ベースの関数の要約も非常に興味深いかもしれません: http://gruntthepeon.free.fr/ssemath/

3
Necrolis

Math.hの正弦関数よりも少なくとも2倍速い高速正弦関数をCPU側に実装しましたが、非常に小さなルックアップテーブル(20浮動小数点数)を使用しました。精度もまったく悪くありません。平均相対エラー率は0.095%です。 http://www.hevi.info/tag/fast-sine-function/ から確認できます。

メソッドの説明は非常に単純で、小さいaのsin(a)= a * pi/180(証明については上記のリンクを参照)の事実に依存しています。

enter image description here

一部の三角法

角度が0から10の場合、上記の式で比較的正確な結果を得ることができますが、精度が失われると角度が広くなります。したがって、10未満の角度の式を使用する必要がありますが、どうですか?

答えは三角関数の正弦加算式です。

sin(a + b)= sin(a)cos(b)+ sin(b)cos(a)

「b」を10未満に保つことができる場合は、式を使用して、2つのアーキメティック演算で正弦を見つけることができます。

次に、71.654の正弦値が求められたとします。

a = 70

b = 1.654

そして、

sin(71.654)= sin(70 + 1.654)= sin(70)cos(1.654)+ sin(1.654)cos(70)

この式では、sin(1.654)部分の高速計算を使用できますが、残りの部分では、残念ながらサインテーブルとコサインテーブルが必要です。良い点は、正弦には10の乗算、余弦には0〜10の自然数の角度だけが必要なことです。

2
hevi

A)小さなパーセントを節約しようとすることは、非常に満足できるものではありません。 100時間ではなく97時間で終了するのは、まだ長い時間です。

B)プロファイルを作成し、トリガー関数に必要以上の時間がかかると言います。いくら?残りの時間はどうですか?大きな魚を揚げる可能性は十分にあります。ほとんどのプロファイラー gprofの概念に基づく は、時間を大幅に節約するために集中できるスタック中の呼び出しについては説明しません。 ここに例があります。

2
Mike Dunlavey

昔、遅いマシンでは、事前に計算された値を持つ配列を使用していました。 this :のような独自の精度で計算する別のオプション(「シリーズ定義」を探す)

1
Yuriy Vikulov

this を見ることができます。それは罪、cosを最適化することについて話します。

1
mAc

2〜3%のゲインの場合、これはほぼ間違いなく不正確、エラー、仮定が真実ではない(たとえば、[-1,-1])など。これを膨大な数のマシンで実行することを計画している場合を除きます(2〜3%は、数千ドルまたは数百万ドルの電力とマシンの償却コストを表します)。

つまり、達成しようとしていることについてドメイン固有の知識がある場合は、計算を2倍以上高速化できる可能性があります。たとえば、常に同じ値のsincosが必要な場合は、コード内で互いに近い値を計算し、コンパイラがそれらをFSINCOSアセンブリ命令に変換することを確認します(-を参照)。 この質問 )。関数の全範囲のごく一部のみが必要な場合は、一連の低次多項式を使用し、その後にニュートン法を反復して、完全なマシン精度(または必要なだけ)を取得できます。繰り返しになりますが、これは、一部の値のみが必要であることがわかっている場合(たとえば、 sin(x)がゼロに近いxに近く、ゼロに近い値しか必要としない場合は、必要な項の数を大幅に減らすことができます。

しかし、繰り返しますが、私の第一のアドバイスは次のとおりです。2〜3%の価値はありません。これを最適化する前に、使用するアルゴリズムとその他の潜在的なボトルネック(たとえば、mallocが時間を使いすぎているか?)についてもっとよく考えてください。

0
Rex Kerr