SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Windows実行ファイル「EXE」の謎に迫る

SSE2による浮動小数演算の仕組みと検証

Windows実行ファイル「EXE」の謎に迫る 最終回

  • X ポスト
  • このエントリーをはてなブックマークに追加

ダウンロード ExeCreator (2.1 MB)
ダウンロード FpuTest (3.7 MB)

SSE2のコード生成サンプル

 さて、おなじみのコーナーがやってまいりました。前回のAMD64に引き続き、SSE2のネイティブコードを生成できるモジュールを作ってみましょう。

 XMMレジスタの指定方法は汎用レジスタと同じにできるため、機械語のフォーマットは比較的きれいな形になります。要は、以前解説を行った__op_format関数にSSE2のオペコードを引き渡すだけで、上記で紹介したようなSSE2に関するネイティブコードを生成できてしまうという訳です。

 ただ単にSSE2を活用した何らかの浮動小数演算プログラムを作ってもつまらないので、同様の動きをFPUで行うプログラムも別途用意してみます。さりげなくベンチマークにも挑戦してみましょう!

 第5回『x86系CPUのネイティブコードを解析する』で作成した「opcode.cpp」に下記の関数を追加します。これらは、先ほど解説したSSE2に対応している機械語命令を生成するための関数です。

SSE2対応の機械語生成モジュール
void op_movlpd_MR(int xmm_reg,int base_reg,int offset,char mod){
    //movlpd qword ptr[base_reg+offset],xmm_reg
    __op_format((char)0x66,(char)0x0F,(char)0x13,
                xmm_reg,base_reg,offset,mod);
}
void op_movlpd_RM(int xmm_reg,int base_reg,int offset,char mod){
    //movlpd xmm_reg,qword ptr[base_reg+offset]
    __op_format(0,(char)0x66,(char)0x0F,(char)0x12,
                xmm_reg,base_reg,offset,mod);
}
void op_movsd_RR(int xmm_reg1,int xmm_reg2){
    if(xmm_reg1==xmm_reg2) return;

    //movsd xmm_reg1,xmm_reg2
    __op_format((char)0xF2,(char)0x0F,(char)0x10,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}
void op_movsd_MR(int xmm_reg,int base_reg,int offset,char mod){
    //movsd qword ptr[reg+offset],xmm_reg
    //movsd qword ptr[reg],xmm_reg
    __op_format(0,(char)0xF2,(char)0x0F,(char)0x11,
                xmm_reg,base_reg,offset,mod);
}
void op_movss_RR(int xmm_reg1,int xmm_reg2){
    if(xmm_reg1==xmm_reg2) return;

    //movss xmm_reg1,xmm_reg2
    __op_format((char)0xF3,(char)0x0F,(char)0x10,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}
void op_movss_RM(int xmm_reg,int base_reg,int offset,char mod){
    //movss xmm_reg,dword ptr[base_reg+offset]
    __op_format((char)0xF3,(char)0x0F,(char)0x10,
                xmm_reg,base_reg,offset,mod);
}
void op_movss_MR(int xmm_reg,int base_reg,int offset,char mod){
    //movss dword ptr[reg+offset],xmm_reg
    //movss dword ptr[reg],xmm_reg
    __op_format((char)0xF3,(char)0x0F,(char)0x11,
                xmm_reg,base_reg,offset,mod);
}

void op_movd_RX(int reg,int xmm_reg){
    __op_format((char)0x66,(char)0x0F,(char)0x7E,
                xmm_reg,reg,0,MOD_REG);
}

void op_cvtsd2ss(int xmm_reg1,int xmm_reg2){
    //cvtsd2ss xmm_reg1,xmm_reg2

    __op_format((char)0xF2,(char)0x0F,(char)0x5A,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}
void op_cvtss2sd(int xmm_reg1,int xmm_reg2){
    //cvtss2sd xmm_reg1,xmm_reg2

    __op_format((char)0xF3,(char)0x0F,(char)0x5A,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}
void op_cvttsd2si(int reg,int xmm_reg){
    //cvttsd2si reg,xmm_reg

    __op_format((char)0xF2,(char)0x0F,(char)0x2C,
                reg,xmm_reg,0,MOD_REG);
}
void op_cvttss2si(int reg,int xmm_reg){
    //cvttss2si reg,xmm_reg

    __op_format((char)0xF3,(char)0x0F,(char)0x2C,
                reg,xmm_reg,0,MOD_REG);
}
void op_cvtsi2sd(int xmm_reg,int reg){
    //cvtsi2sd xmm_reg,reg

    __op_format((char)0xF2,(char)0x0F,(char)0x2A,
                xmm_reg,reg,0,MOD_REG);
}
void op_cvtsi2ss(int xmm_reg,int reg){
    //cvtsi2ss xmm_reg,reg

    __op_format((char)0xF3,(char)0x0F,(char)0x2A,
                xmm_reg,reg,0,MOD_REG);
}
void op_comisd(int xmm_reg1,int xmm_reg2){
    //comisd xmm_reg1,xmm_reg2

    __op_format((char)0x66,(char)0x0F,(char)0x2F,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}
void op_comiss(int xmm_reg1,int xmm_reg2){
    //comiss xmm_reg1,xmm_reg2

    __op_format(0,(char)0x0F,(char)0x2F,xmm_reg1,
                xmm_reg2,0,MOD_REG);
}

void op_addsd_RR(int xmm_reg1,int xmm_reg2){
    if(xmm_reg1==xmm_reg2) return;

    //addsd xmm_reg1,xmm_reg2
    __op_format((char)0xF2,(char)0x0F,(char)0x58,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}
void op_subsd_RR(int xmm_reg1,int xmm_reg2){
    if(xmm_reg1==xmm_reg2) return;

    //subsd xmm_reg1,xmm_reg2
    __op_format((char)0xF2,(char)0x0F,(char)0x5C,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}
void op_mulsd_RR(int xmm_reg1,int xmm_reg2){
    if(xmm_reg1==xmm_reg2) return;

    //mulsd xmm_reg1,xmm_reg2
    __op_format((char)0xF2,(char)0x0F,(char)0x59,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}
void op_divsd_RR(int xmm_reg1,int xmm_reg2){
    if(xmm_reg1==xmm_reg2) return;

    //divsd xmm_reg1,xmm_reg2
    __op_format((char)0xF2,(char)0x0F,(char)0x5E,
                xmm_reg1,xmm_reg2,0,MOD_REG);
}

 それでは、これらの関数を活用して簡単な計算プログラムを組み立ててみましょう。下記のソースコードは、

i=(i+2-1)*10/10

 という計算を行い、iの値が1億になったところで計算をストップするという四則演算を活用した単純なベンタマークプログラムです。ようは、足し算、引き算、乗算、除算をそれぞれ1億回行う処理をします。

SSE2命令を活用したサンプル生成
void create_native_buffer(void){
    //ネイティブコードを生成

    //mov eax,1
    op_mov_RV(REG_EAX,1);

    //mov ecx,2
    op_mov_RV(REG_ECX,2);

    //mov edx,10
    op_mov_RV(REG_EDX,10);

    //mov ebx,100000000
    op_mov_RV(REG_EBX,100000000);

    //cvtsi2sd xmm1,eax
    op_cvtsi2sd(REG_XMM1,REG_EAX);

    //cvtsi2sd xmm2,ecx
    op_cvtsi2sd(REG_XMM2,REG_ECX);

    //cvtsi2sd xmm3,edx
    op_cvtsi2sd(REG_XMM3,REG_EDX);

    //cvtsi2sd xmm4,ebx
    op_cvtsi2sd(REG_XMM4,REG_EBX);

    //movsd xmm0,xmm1
    op_movsd_RR(REG_XMM0,REG_XMM1);

//loop:

    //addsd xmm0,xmm2
    op_addsd_RR(REG_XMM0,REG_XMM2);

    //subsd xmm0,xmm1
    op_subsd_RR(REG_XMM0,REG_XMM1);

    //mulsd xmm0,xmm3
    op_mulsd_RR(REG_XMM0,REG_XMM3);

    //divsd xmm0,xmm3
    op_divsd_RR(REG_XMM0,REG_XMM3);

    //comisd xmm0,xmm4
    op_comisd(REG_XMM0,REG_XMM4);

    //jne -22(loopへ)
    op_jne(-22);

    //以下、メッセージボックスを表示するためのコード

    //push 0
    op_push_V(0);

    //push dword ptr["SSE2 TEST"]
    op_push_V(0x00403013);

    //push dword ptr["計算が終了しました"]
    op_push_V(0x00403000);

    //push 0
    op_push_V(0);

    //call dword ptr[MessageBoxA in import_table]
    op_call_M(0x0040205E);

    //ret 0
    op_ret();
}

 今回は、前回出てこなかったjne命令(op_jne関数で生成)を追加しています。このような小修正はソースコードをダウンロードしてご確認ください。

実行してみよう

 さて、コーディングを終えたら、プログラムを実行してターゲットとなる「test.exe」を生成してみましょう。

 生成される「test.exe」を実行すると、1億回、四則演算が行われた後、「計算が終了しました」というメッセージが表示されるはずです。CPU速度により、処理を終えるまでの時間はバラバラですが、筆者の環境では4秒程度で処理を終えました。

SSE2サンプルの実行結果
SSE2サンプルの実行結果

FPUと比較

 では実際にFPUを活用して同様の処理をさせたとき、実行速度に差が出るのかを検証してみます。ここからはVC++の機能の一つである、インラインアセンブラを使ってプログラミングしますので、「test.exeを生成して…」というややこしいことは行いません。

FPUを活用したサンプル
// FpuTest.cpp : コンソール アプリケーションのエントリ
// ポイントを定義します。
//

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    int i1,i2,i3,i4;
    _asm{
        mov eax,1
        mov i1,eax
        mov eax,2
        mov i2,eax
        mov eax,10
        mov i3,eax
        mov eax,100000000
        mov i4,eax
        fild i1
start:
        fild i2
        faddp st(1),st
        fild i1
        fsubp st(1),st
        fild i3
        fmulp st(1),st
        fild i3
        fdivp st(1),st
        fild i4
        fcomp
        fnstsw ax
        test ah,0x40
        je start
    }

    MessageBox(0,"計算が終了しました","FPU TEST",MB_OK);
    return 0;
}

 このプログラムをコンパイル&実行し、どれくらいの時間で処理を終えるのかを確認してみましょう。

 筆者の環境での測定結果は、SSE2のものと同じく、4秒程度でした。SSE2とFPUとで処理速度に差が出ることを信じていたのですが、残念ながらここまで単純なプログラムだとその差を体感することはできませんでした。やはり、SSE2の威力を発揮するためには、並列演算をバカみたいに繰り返さなければならないという結論にたどり着くのですね……。

まとめ

 最後に、ちょっとしっくりとしない終わり方になってしまいましたが、SSE2の魅力が並列処理に隠されていることは明確なようです。今回紹介したSSE2対応の機械語命令は、倍精度浮動小数点に関するものばかりでしたので、興味がある方は、並列演算に対応した機械語命令について突っ込んでいけば面白い展開になりそうです。

 さて、今回まで8回分の内容を通して、Windows実行ファイル「EXE」の謎に迫ってきました。結局のところ、EXEファイルにはネイティブコード以外にもさまざまな情報が格納されており、それらを統括管理しているのがPEヘッダ・セクション情報であるということがお分かりいただけたのではないかと思います。EXEファイルの一部であるネイティブコードに関しても、数え切れないほどのx86命令が存在し、昨今投入されたAMD64が拡張を推し進めているという非常に目まぐるしい状況となっています。その中でも、SSE2などの新たな浮動小数演算のための機構に着目し、どのような点に新アーキテクチャのメリットがあるのかを少しだけでも実感できたこの企画、筆者の私も今一度、勉強させられました。

 Windows実行ファイル「EXE」の謎に迫る今回の企画はこれで一区切りとさせていただきます。相変わらずですが、筆者はこれらの技術を活用し、新たな言語ソフトウェアの開発を進めていくことにします。

 今回の企画で紹介したさまざまな情報、テクニックを活用して自分だけのアセンブルソフトやコンパイラを開発される方が一人でも多くいれば、私としては大満足です。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Windows実行ファイル「EXE」の謎に迫る連載記事一覧

もっと読む

この記事の著者

山本 大祐(ヤマモト ダイスケ)

普段はActiveBasicと周辺ツールの開発を行っています。最近は諸先輩方を見習いながら勉強中の身。AB開発日記

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/458 2006/08/04 00:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング