web-dev-qa-db-ja.com

sqrtの非常に高速なバージョンをロールすることは可能ですか?

私がプロファイリングしているアプリで、一部のシナリオでは、この関数が合計実行時間の10%を超えることができることがわかりました。

卑劣な浮動小数点トリックを使用した高速sqrt実装の何年にもわたる議論を見てきましたが、そのようなものが最新のCPUでは古くなっているかどうかはわかりません。

参考のために、MSVC++ 2008コンパイラを使用していますが、sqrtを使用してもオーバーヘッドはそれほど大きくならないと思います。

modf 関数に関する同様の説明については、こちらも参照してください。

編集:参考のために、 これ は広く使用されている方法の1つですが、実際にははるかに高速ですか?最近、SQRTは何サイクルですか?

26
Mr. Boy

はい、策略がなくても可能です:

1)速度の精度を犠牲にする:sqrtアルゴリズムは反復的で、反復回数を減らして再実装します。

2)ルックアップテーブル:反復の開始点のみ、または補間と組み合わせて、そこまでたどり着きます。

3)キャッシング:同じ制限された値のセットを常にsqrtしていますか?その場合、キャッシングはうまく機能します。これは、同じサイズの多くの図形に対して同じものが計算されるグラフィックアプリケーションで役立つため、結果を有効にキャッシュできます。

24
James

ここにすばらしい比較表があります: http://assemblyrequired.crashworks.org/timing-square-root/

要するに、SSE2のssqrtsはFPU fsqrtよりも約2倍高速であり、近似+反復はそれよりも約4倍高速です(全体で8倍)。

また、単精度のsqrtを取得しようとしている場合は、それが実際に取得しているものであることを確認してください。 float引数をdoubleに変換し、倍精度のsqrtを呼び出してから、floatに戻すコンパイラを少なくとも1つは聞いたことがあります。

16
celion

algorithmsを変更するよりもimplementationsを変更するよりも、速度を向上させる可能性が非常に高い:呼び出しを速くする代わりにsqrt() lessを呼び出す。 (そして、これが不可能だと思った場合、sqrt()の改善点はそれだけです。平方根の計算に使用されるalgorithmの改善点です。)

これは非常に頻繁に使用されるため、標準ライブラリのsqrt()の実装は、一般的なケースにほぼ最適です。アルゴリズムがいくつかのショートカットを取ることができる制限されたドメインがない場合(たとえば、必要な精度が低い場合)は、誰かがより高速な実装を考え出すことはほとんどありません。

この関数は実行時間の10%を使用するため、std::sqrt()の75%の時間しかかからない実装を思いついたとしても、これでも実行時間は短くなります。 2,5%による。ほとんどのアプリケーションでは、ユーザーが時計を使用して測定する場合を除いて、これに気づくことすらありません。

10
sbi

sqrtはどの程度正確である必要がありますか?妥当な近似を非常に迅速に得ることができます。Quake3の優れた 逆平方根 関数を参考にしてください(コードはGPLになっているため、直接統合したくない場合があります)。

3
jemfinch

これを修正したかどうかはわかりませんが、私は以前に読んだことがあり、最速のことはsqrt関数をインラインアセンブリバージョンに置き換えることです。

あなたは選択肢の負荷の説明を見ることができます ここ

最高はこの魔法の断片です:

double inline __declspec (naked) __fastcall sqrt(double n)
{
    _asm fld qword ptr [esp+4]
    _asm fsqrt
    _asm ret 8
} 

同じ精度で、標準のsqrt呼び出しより約4.7倍高速です。

1
will

以下は、わずか8KBのルックアップテーブルを使用した高速な方法です。間違いは結果の約0.5%です。テーブルを簡単に拡大できるため、間違いを減らすことができます。通常のsqrt()よりも約5倍速く実行されます

// LUT for fast sqrt of floats. Table will be consist of 2 parts, half for sqrt(X) and half for sqrt(2X).
const int nBitsForSQRTprecision = 11;                       // Use only 11 most sagnificant bits from the 23 of float. We can use 15 bits instead. It will produce less error but take more place in a memory. 
const int nUnusedBits   = 23 - nBitsForSQRTprecision;       // Amount of bits we will disregard
const int tableSize     = (1 << (nBitsForSQRTprecision+1)); // 2^nBits*2 because we have 2 halves of the table.
static short sqrtTab[tableSize]; 
static unsigned char is_sqrttab_initialized = FALSE;        // Once initialized will be true

// Table of precalculated sqrt() for future fast calculation. Approximates the exact with an error of about 0.5%
// Note: To access the bits of a float in C quickly we must misuse pointers.
// More info in: http://en.wikipedia.org/wiki/Single_precision
void build_fsqrt_table(void){
    unsigned short i;
    float f;
    UINT32 *fi = (UINT32*)&f;

    if (is_sqrttab_initialized)
        return;

    const int halfTableSize = (tableSize>>1);
    for (i=0; i < halfTableSize; i++){
         *fi = 0;
         *fi = (i << nUnusedBits) | (127 << 23); // Build a float with the bit pattern i as mantissa, and an exponent of 0, stored as 127

         // Take the square root then strip the first 'nBitsForSQRTprecision' bits of the mantissa into the table
         f = sqrtf(f);
         sqrtTab[i] = (short)((*fi & 0x7fffff) >> nUnusedBits);

         // Repeat the process, this time with an exponent of 1, stored as 128
         *fi = 0;
         *fi = (i << nUnusedBits) | (128 << 23);
         f = sqrtf(f);
         sqrtTab[i+halfTableSize] = (short)((*fi & 0x7fffff) >> nUnusedBits);
    }
    is_sqrttab_initialized = TRUE;
}

// Calculation of a square root. Divide the exponent of float by 2 and sqrt() its mantissa using the precalculated table.
float fast_float_sqrt(float n){
    if (n <= 0.f) 
        return 0.f;                           // On 0 or negative return 0.
    UINT32 *num = (UINT32*)&n;
    short e;                                  // Exponent
    e = (*num >> 23) - 127;                   // In 'float' the exponent is stored with 127 added.
    *num &= 0x7fffff;                         // leave only the mantissa 

    // If the exponent is odd so we have to look it up in the second half of the lookup table, so we set the high bit.
    const int halfTableSize = (tableSize>>1);
    const int secondHalphTableIdBit = halfTableSize << nUnusedBits;
    if (e & 0x01) 
        *num |= secondHalphTableIdBit;  
    e >>= 1;                                  // Divide the exponent by two (note that in C the shift operators are sign preserving for signed operands

    // Do the table lookup, based on the quaternary mantissa, then reconstruct the result back into a float
    *num = ((sqrtTab[*num >> nUnusedBits]) << nUnusedBits) | ((e + 127) << 23);
    return n;
}
1
DanielHsH