2009年11月24日火曜日

fsincosでプチ高速化

Check
ここ最近、透視投影行列についてお勉強していました。透視投影行列を求めるために、D3DXMatrixPerspectiveFovLH()関数と同じ機能を持つ関数を自前でこしらえるとなると、どうしても速度的に問題が出てきます。

MSDNによるとD3DXMatrixPerspectiveFovLH()関数の中身は以下の通り。
w       0       0               0
0       h       0               0
0       0       zf/(zf-zn)      1
0       0       -zn*zf/(zf-zn)  0
where:
h is the view space height. It is calculated from 
h = cot(fovY/2);

w is the view space width. It is calculated from
w = h / Aspect.
ここで問題になるのがcot(fovY/2)の部分。コタンジェントは tanθの逆数らしいので、つまるところcosθ/sinθ。

毎フレーム呼びたい関数にsin()とcos()関数が両方とも入っちゃってるわけです。実際、素直に書き起こしたコードとD3DXMatrixPerspectiveFovLH()関数の速度を比較してみたら、1000万回のループでおおよそ1.5~2倍くらいの時間差が! ボトルネックは予想通り、sin()とcos()でした。

そこでsin()とcos()の部分だけインラインアセンブラで高速化に挑戦してみることにしました。 使った命令は8087命令セット?なので、まあ今日のx86プロセッサなら問題なく使えると思います。

ただ、この8087命令は使用するレジスタがちと特殊なようで、汎用レジスタからではfsincos命令の引数も戻り値も受け取ることはできないっぽい。長々と書くのもアレなんで、ズバリ答えが載ってるサイトを発見しました。ありがたく利用させていただくことに。

float angle, cosx, sinx;//引用元はdoubleでしたがfloatに変更させていただきました
_asm
{
 fld angle ;レジスタスタックに角度をプッシュ
 fsincos  ;fsincos命令で一気にsin(angle)とcos(angle)を計算。答えはST(0)にcos,ST(1)にsinが入っている。
 fstp cosx ;ST(0)スタックトップからcosをポップ。ST(1)がST(0)にスライドする。
 fstp sinx ;同じくスタックトップからsinをポップ。
}

たったこれだけのコードで、sinθとcosθの値が同時に求められるんですね。これは便利だ。速度もd3dxをぶっちぎりで上回る200%以上の効果。ここまで速いと、エラーチェックとか要るのかなぁとかえって不安になってしまいますが…。まぁ気にしない気にしない…。

 とっても参考になったサイト
SSE2を用いたPOV-Rayの最適化に関する考察
Intel x86 Instruction Reference
SIMPLY FPU

0 件のコメント:

コメントを投稿