web-dev-qa-db-ja.com

方法:x86でpow(real、real)

X86アセンブリでのpow(real, real)の実装を探しています。また、アルゴリズムの仕組みを理解したいと思います。

31
Maciej Ziarko

2^(y*log2(x))として計算するだけです。

Y * log2(x)を計算するx86命令FYL2Xと、べき乗を行うx86命令F2XM1があります。 F2XM1は[-1,1]の範囲の引数を必要とするため、整数部分と剰余を抽出し、剰余を累乗し、FSCALEを使用して適切な2の累乗で結果をスケーリングするためにコードを追加する必要があります。

64
Eugene Smith

OK、私はあなたがお勧めしたようにx86にpower(double a, double b, double * result);を実装しました。

コード: http://Pastebin.com/VWfE9CZT

%define a               QWORD [ebp+8]
%define b               QWORD [ebp+16]
%define result          DWORD [ebp+24]
%define ctrlWord            Word [ebp-2]
%define tmp             DWORD [ebp-6]

segment .text
    global power

power:
    Push ebp
    mov ebp, esp
    sub esp, 6
    Push ebx

    fstcw ctrlWord
    or ctrlWord, 110000000000b
    fldcw ctrlWord

    fld b
    fld a
    fyl2x

    fist tmp

    fild tmp
    fsub
    f2xm1
    fld1
    fadd
    fild tmp
    fxch
    fscale

    mov ebx, result
    fst QWORD [ebx]

    pop ebx
    mov esp, ebp
    pop ebp
    ret
16
Maciej Ziarko

これが「The Svin」によるメインアルゴリズムを使用した私の関数です。私はそれを__fastcallと__declspec(naked)の装飾でラップし、base/xが正であることを確認するコードを追加しました。 xが負の場合、FPUは完全に失敗します。 'x'符号ビットをチェックし、さらに 'y'の奇数/偶数ビットを考慮して、終了後に符号を適用する必要があります。 Lemmeは、ランダムな読者にあなたがどう思うかを知っています。可能であれば、x87 FPUコードでさらに優れたバージョンを探します。 Microsoft VC++ 2005でコンパイル/動作します。これは、さまざまな理由でいつも私が使い続けているものです。

互換性v。ANSI pow(x、y):とても良いです!より高速で予測可能な結果、負の値が処理され、無効な入力に対するエラーフィードバックはありません。ただし、「y」が常にINT/LONGになる可能性があることがわかっている場合は、このバージョンを使用しないでください。 Agner Fogのバージョンを投稿して、非常に遅いFSCALEを回避するためにいくつかの調整を加えました。プロファイルを検索してください!彼はこれらの限られた状況下で最速のx87/FPU方法です!

extern double __fastcall fs_Power(double x, double y);

// Main Source: The Svin
// pow(x,y) is equivalent to exp(y * ln(x))
// Version: 1.00

__declspec(naked) double __fastcall fs_Power(double x, double y) { __asm {
    LEA   EAX, [ESP+12]         ;// Save 'y' index in EAX
    FLD   QWORD PTR [EAX]       ;// Load 'y' (exponent) (works positive OR negative!)
    FIST  DWORD PTR [EAX]       ;// Round 'y' back to INT form to test for odd/even bit
    MOVZX EAX, Word PTR [EAX-1] ;// Get x's left sign bit AND y's right odd/even bit!
    FLD   QWORD PTR [ESP+4]     ;// Load 'x' (base) (make positive next!)
    FABS            ;// 'x' MUST be positive, BUT check sign/odd bits pre-exit!
    AND   AX, 0180h ;// AND off all bits except right 'y' odd bit AND left 'x' sign bit!
    FYL2X       ;// 'y' * log2 'x' - (ST(0) = ST(1) * log2 ST(0)), pop
    FLD1        ;// Load 1.0f: 2 uses, mantissa extract, add 1.0 back post-F2XM1
    FLD   ST(1) ;// Duplicate current result
    FPREM1      ;// Extract mantissa via partial ST0/ST1 remainder with 80387+ IEEE cmd
    F2XM1       ;// Compute (2 ^ ST(0) - 1)
    FADDP ST(1), ST ;// ADD 1.0f back! We want (2 ^ X), NOT (2 ^ X - 1)!
    FSCALE      ;// ST(0) = ST(0) * 2 ^ ST(1) (Scale by factor of 2)
    FFREE ST(1) ;// Maintain FPU stack balance
;// Final task, make result negative if needed!
    CMP   AX, 0180h    ;// Combo-test: Is 'y' odd bit AND 'x' sign bit set?
    JNE   EXIT_RETURN  ;// If positive, exit; if not, add '-' sign!
        FCHS           ;// 'x' is negative, 'y' is ~odd, final result = negative! :)
EXIT_RETURN:
;// For __fastcall/__declspec(naked), gotta clean stack here (2 x 8-byte doubles)!
    RET   16     ;// Return & pop 16 bytes off stack
}}

さて、この実験を締めくくるために、私はRDTSC CPUタイムスタンプ/クロックカウンター命令を使用してベンチマークテストを実行しました。また、「SetPriorityClass(GetCurrentProcess()、HIGH_PRIORITY_CLASS);」を使用してプロセスを高優先度に設定するというアドバイスに従いました。そして、私は他のすべてのアプリを閉じました。

結果:レトロなx87 FPU数学関数「fs_Power(x、y)」は、かなり長いコードのブランチを使用するMSCRT2005 pow(x、y)バージョンよりも50〜60%高速ですSSEコードのブランチ64ビットの> Pentium4 + CPUを検出した場合、「_ pow_pentium4:」というラベルが付けられます。

注:(1)CRT pow()には〜33マイクロ秒の初期化ブランチがあり、このテストでは46,000を示しています。 1200から3000サイクル後は通常の平均で動作します。手作りのx87 FPUの美しさは一貫して実行され、最初の呼び出しで初期ペナルティはありません!

(2)CRT pow()はすべてのテストに失敗しましたが、DID ONE領域で勝ちました:ワイルドで巨大な範囲外/オーバーフロー値を入力した場合、すぐにエラーが返されました。ほとんどのアプリは、通常/通常の使用ではエラーチェックを必要としないため、無関係です。

https://i.postimg.cc/QNbB7ZVz/FPUv-SSEMath-Power-Proc-Test.png

2番目のテスト(イメージスナップ後にテキストをコピー/貼り付けるためにもう一度実行する必要がありました):

 x86 fs_Power(2, 32): CPU Cycles (RDTSC): 1248
MSCRT SSE pow(2, 32): CPU Cycles (RDTSC): 50112

 x86 fs_Power(-5, 256): CPU Cycles (RDTSC): 1120
MSCRT SSE pow(-5, 256): CPU Cycles (RDTSC): 2560

 x86 fs_Power(-35, 24): CPU Cycles (RDTSC): 1120
MSCRT SSE pow(-35, 24): CPU Cycles (RDTSC): 2528

 x86 fs_Power(64, -9): CPU Cycles (RDTSC): 1120
MSCRT SSE pow(64, -9): CPU Cycles (RDTSC): 1280

 x86 fs_Power(-45.5, 7): CPU Cycles (RDTSC): 1312
MSCRT SSE pow(-45.5, 7): CPU Cycles (RDTSC): 1632

 x86 fs_Power(72, -16): CPU Cycles (RDTSC): 1120
MSCRT SSE pow(72, -16): CPU Cycles (RDTSC): 1632

 x86 fs_Power(7, 127): CPU Cycles (RDTSC): 1056
MSCRT SSE pow(7, 127): CPU Cycles (RDTSC): 2016

 x86 fs_Power(6, 38): CPU Cycles (RDTSC): 1024
MSCRT SSE pow(6, 38): CPU Cycles (RDTSC): 2048

 x86 fs_Power(9, 200): CPU Cycles (RDTSC): 1152
MSCRT SSE pow(9, 200): CPU Cycles (RDTSC): 7168

 x86 fs_Power(3, 100): CPU Cycles (RDTSC): 1984
MSCRT SSE pow(3, 100): CPU Cycles (RDTSC): 2784

実際のアプリケーションはありますか?はい! Pow(x、y)は、CDのWAVEフォーマットをOGGに、またはその逆にエンコード/デコードするために頻繁に使用されます。 60分のWAVEデータ全体をエンコードする場合は、ここで時間を節約することが重要になります。 OGG/libvorbisでは、acos()、cos()、sin()、atan()、sqrt()、ldexp()(非常に重要)などの多くの数学関数が使用されています。このように微調整されたバージョンでは、エラーチェックを気にする/必要としないで、多くの時間を節約できます!!

私の実験は、NSISインストーラーシステム用のOGGデコーダーを構築した結果であり、その結果、アルゴリズムに必要なすべてのMath "C"ライブラリー関数を、上記のものと置き換えることができました。まあ、ALMOST、x86にはacos()が必要ですが、それでも何も見つかりません...

よろしく、そしてこれがいじくりを好む他の誰にとっても役立つことを願っています!

1
John Doe