ラベル アセンブラ の投稿を表示しています。 すべての投稿を表示
ラベル アセンブラ の投稿を表示しています。 すべての投稿を表示

2010年2月12日金曜日

slaxでレイトレーサ

Check
自作のレイトレーサをslaxに移植して「もらった」。学校の先生が空いた時間を使ってやってくださったようだ。
これにより、企業への作品提出にOSごと送りつけるという、面白いことができるわけだ(迷惑かな?)。

ただ、VC++とG++の違いを意識しなくてはならなくなった。具体的にはインラインアセンブラの構文や一部の関数が異なり、現時点では_MSC_VERマクロの有無で切り分けるようにしている。
float Vec::length() const
{
    float len = x*x+y*y+z*z;
#ifdef _MSC_VER
    __asm
    {
        fld    len
        fsqrt
        fstp   len
    }
#else
    len = sqrt(len);
#endif
    return len;
}
速度重視のため、アセンブラ部分はかなりの部分をベタ書きのハードコーディングにしていたが、インライン関数に置き換えるなどしないとメンテしづらいことがわかった。

wxWidgetsはソースコードレベルでの互換性を保障しているわけだが、実際には結構な部分に手を加えなければならなかった。今後は、移植を前提とした書き方に慣れる必要があるだろう。

C#やJavaなどのVM上で動く言語が主流になるのも頷ける。

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

2009年11月15日日曜日

VC++でインラインアセンブラ

Check
おもろそうな記事を見つけたので試してみた。
生でアセンブル言語を使用するのはちと敷居が高いので、インラインアセンブラから入ってみようという企画。ゆとりPGの自分にはちょうどいい。

用語の整理
  • ロード メモリからレジスタに値を読み込むこと
  • ストア レジスタの値をメモリに書き込むこと
  • 汎用レジスタ いろんな計算に使える便利なやつ。EAX~EDXまであるようだ

とりあえず実践。出力は普通にcoutを使うよ。
#include <iostream>
using namespace std;
// iに100を代入して表示するだけのプログラム
int main()
{
    int i;
    __asm
    {
        mov i, 100;
    }
    cout << i << endl;
}
次にレジスタを使ってみる。
#include <iostream>
using namespace std;
// レジスタに0xFFFF0000をロードしてiにストアするプログラム
int main()
{
    int i;
    __asm
    {
        mov eax, 0xFFFF0000;
        mov i, eax;
    }
    cout << hex << i << endl;// ffff0000
}
配列を扱うときは、インデクスの値に注意。インデクスを1つ増やしてもアセンブラでは1バイトしか移動しないようだ。そこで、Cでいうところの[sizeof(int)]と同じ役割を持つ[type 変数名]をインデクス値にかけてやる必要がある。
#include <iostream>
using namespace std;
//配列arrayに値を代入するプログラム
int main()
{
    int array[4];

    __asm
    {
        mov array[0], 100;
        mov array[1*type array], 200;//1*sizeof(int)と同じ。これでポインタが4バイト移動する
        mov array[2*type array], 300;
        mov array[3*type array], 400;
    }
 
    for(int i=0;i<4; ++i)
    {
        cout << "array[" << i << "] = " << array[i] << endl;
    }
    return 0;
}

みんな大好きポインタ篇
#include <iostream>
using namespace std;
int main()
{
    int i;
    int *p = &i;//pにiのアドレスを格納
    __asm
    {
        mov ebx, p;ポインタ用のレジスタEBXにpをロード
        mov [ebx], 100;*pに100をストア
    }
    cout << i << endl;//100
    return 0;
}

ポインタが指す実体を参照したい場合は、レジスタ名を[]で囲むといいようだ。ポインタ変数pに対する*pと同じことが、レジスタEBXに対する[EBX]で行えるというわけだ。

なお、__asmブロック内ではC/C++のコメント//や/**/が使えるほか、;の後ろも1行コメントとして認識されるらしい。

構造体も扱えちゃう
#include <iostream>
using namespace std;
//適当に構造体を作成
struct FOO
{
    int val1, val2;
};

int main()
{
    FOO a;//実体を作る
    FOO *p = &a;//aへのポインタ
    __asm
    {
        mov ebx, p//aのアドレスをレジスタEBXにロード
        mov [ebx]a.val1, 100/*ポインタを参照してa.val1に100をストア*/
        mov [ebx]a.val2, 200;同様にa.val2に200をストア
    }
    cout << "a.val1 = " << a.val1 << endl;// a.val1 = 100
    cout << "a.val2 = " << a.val2 << endl;// a.val2 = 200
    return 0;
}
次回は演算を勉強しよう…