最近のコンパイラは賢いためかなり最適化されたコードを吐いて くれますが、それでもやはりアセンブラを使った方が(よっぽど間抜けな コードを書かない限り)一般には速くなります。しかし幾つか例外があります。 その一つは FPU 命令を使った場合で、要するに浮動少数演算 を扱う場合です。
詳しくはここを読んで頂くとして、具体的に FPU の何が問題かと言うと、主に
もちろん玉砕覚悟で FPU 命令の手動最適化をすればアセンブラの方が 速くなりますが、暗号のクラックコンテストの様なコンテスト用のプログラム でもない限りあまりやる気がしませんね。FPU の最適化に費やす時間があったら 他の部分のコードを書いていた方が良いです。
ここで具体例(失敗例 (^^;)をあげてみましょう。以前作った高速フーリエ 変換のプログラムがどうも遅い様な気がしたので演算部分をアセンブラで 書き直したのですが、面倒くさかったので FPU の最適化をしないで 馬鹿正直なコードを書いたら逆に遅くなりました(笑)。手動で書いたコードより もコンパイラが吐いたコードの方が 1.2 倍程速かったです。うお、コンパイラに 負けた(^^;
下におまけで載せている VC++6.0 用のコードを見ていただくと分かるように、 フーリエ変換の様な計算では FPU 命令が山の様に出てきます。 こんなものを手動で最適化 する気は起きません。誰か最適化して下さい(笑い)。という訳で、今度から FPU 命令を使うような場合はアセンブラ化しないことにしました(駄目杉)。 ちなみに、固定少数を使うならばアセンブラ化した方がはるかに速くなります( 精度を気にしなければ MMX で同時演算も行えますし)。
重みの sin 値と cos 値(double 型配列、 サイズ (int)(log(データ数)/log(2)+1)*データ数/2) とビット反転用の 最大ビット数を PreCalcPowerSpec() で計算してから、 data に COMPLEX 型のデータを入力し(必ず 2 の倍数個)、 データと重みを CalcPowerSpec() に渡すとパワースペクトルが data の実数パートに(半分だけ)入って戻る。
// 複素型構造体 typedef struct { double r; // 実数パート double i; // 虚数パート }COMPLEX; //------------------------------------ // パワースペクトル計算の前準備 // // バタフライ計算の sin と cos の重みテーブル計算 // ビットの反転で使用する最大 BIT の計算をする BOOL PreCalcPowerSpec(double* lpdCos, // cos テーブル double* lpdSin, // sin テーブル LPDWORD lpdwMaxBit, // 最大ビット数 DWORD dwFftPoint // ポイント数 ){ DWORD i,i2; double dRad; DWORD dwStage = (DWORD)(log(dwFftPoint)/log(2)); // Sin , Cos テーブルセット for(i=1;i< =dwStage;i++){ for(i2=0;i2< dwFftPoint/2;i2++){ dRad = 2.*PI*(double)(i2)/(double)(1<< i); lpdCos[i*dwFftPoint/2+i2] = cos(dRad); lpdSin[i*dwFftPoint/2+i2] = sin(dRad); } } // 最大 BIT の計算 i=dwFftPoint-1; *lpdwMaxBit = 0; while(i>0){i>>=1;(*lpdwMaxBit)++;}; return TRUE; } //------------------------------------ // パワースペクトル計算のアセンブラ版 // FPU に手を出すとろくな目に会わない(--; VOID CalcPowerSpec(COMPLEX* data,// FFT を行うデータ DWORD dwFftPoint, // FFT の点数 DWORD dwMaxBit, // dwFftPoint のビット数 double* dCosTable,// Cos のテーブル double* dSinTable// Sin のテーブル ){ // FFT 用変数 DWORD dwStage; DWORD dwPart; DWORD dwDataHeight,dwDataHeight2; // データのポインタ DWORD dwDataPos; // 雑用 DWORD i,j,k; //------------ // 計算部 dwPart = 1; dwStage = (DWORD)(log(dwFftPoint)/log(2)); _asm{ // for(dwStage = (DWORD)(log(dwFftPoint)/log(2));dwStage>0;dwStage--) L_STAGE: // dwDataHeight = (DWORD)1 << dwStage; // 2^dwStage // dwDataHeight2 = dwDataHeight/2; mov eax,1 mov ecx,dwStage shl eax,cl mov dwDataHeight,eax mov ebx,eax shr ebx,1 mov dwDataHeight2,ebx //-------------------------------- // バタフライ演算開始 // for(j = 0; j < dwPart ;j++){ xor edi,edi // edi = j L_PART: // for(k = 0 ; k < dwDataHeight2 ; k++){ xor esi,esi // esi = k // dwDataPos1 = dwDataHeight*j; // dwDataPos2 = dwDataPos1+dwDataHeight2; // eax = &data[dwDataPos1].r // ebx = &data[dwDataPos2].r mov eax,dwDataHeight mov ebx,dwDataHeight2 imul eax,edi add ebx,eax shl eax,4 // eax*=16 COMPLEX 型は 64*2 bit変数 shl ebx,4 // ebx*=16 add eax,data // ベースを足す add ebx,data L_HEIGHT: // dReal1 = data[dwDataPos1].r; // 実数 1 // dReal2 = data[dwDataPos2].r; // 実数 2 // dImag1 = data[dwDataPos1].i; // 虚数 1 // dImag2 = data[dwDataPos2].i; // 虚数 2 fld qword ptr [eax] // data[dwDataPos1].r fld qword ptr [ebx] // data[dwDataPos2].r fld qword ptr [eax+8] // data[dwDataPos1].i fxch st(1) // キャッシュを考えると fxch 入れた方が良い? fld qword ptr [ebx+8] // data[dwDataPos2].i // スタックの状態 st = i2,r2,i1,r1 // data[dwDataPos1].i = dImag1+dImag2; // 虚数の和 fld st(2) fadd st,st(1) fstp qword ptr [eax+8] // data[dwDataPos1].r = dReal1+dReal2; // 実数の和 fld st(3) fadd st,st(2) fstp qword ptr [eax] // st = i2,r2,i1,r1 //虚数積 // dReal3 = dReal1 - dReal2; // dImag3 = dImag1 - dImag2; fsubp st(2),st fsubp st(2),st // st = i1-i2,r1-r2 = img,real // dCoss = dCosTable[dwStage*dwFftPoint/2+k]; // dSinn = dSinTable[dwStage*dwFftPoint/2+k]; push ebx // ポインタ計算 mov ecx,esi mov ebx,dwStage shl ecx,3 // double 型なので *8 倍 imul ebx,dwFftPoint shl ebx,2 add ebx,ecx // Cos 値 Sin 値 FPU に代入 mov edx,dSinTable mov ecx,dCosTable add edx,ebx add ecx,ebx pop ebx fld qword ptr [edx] //sin fld qword ptr [ecx] //cos fld st(1) fld st(1) // st = cos,sin,cos,sin,imag,real // data[dwDataPos2].r = dReal3 * dCoss + dImag3 * dSinn; // data[dwDataPos2].i = dImag3 * dCoss - dReal3 * dSinn; fmul st,st(5) fxch st(1) fmul st,st(4) faddp st(1),st fstp qword ptr [ebx] // st = cos,sin,imag,real fmul st,st(2) fxch st(1) fmul st,st(3) fsubp st(1),st fstp qword ptr [ebx+8] fstp st(0) fstp st(0) // st = 無し add eax,16 // eax = data[dwDataPos1] += 16 add ebx,16 // ebx = data[dwDataPos2] += 16 // ループ終わり inc esi cmp esi,dwDataHeight2 jnae L_HEIGHT inc edi cmp edi,dwPart jnae L_PART // パートを 2 つに分ける // dwPart *=2; // dwStage-- してループ mov ebx,dwPart mov eax,dwStage shl ebx,1 dec eax mov dwPart,ebx mov dwStage,eax jnz L_STAGE } //-------------------------------- // 要素入れかえとパワースペクトル計算 for(i = 0; i < dwFftPoint ;i+=2){ //ビットを反転して代入する位置を計算 dwDataPos = 0; j = i; for(k=0;k< dwMaxBit;k++){ dwDataPos << = 1; dwDataPos |= (j & 1); j >>= 1; } // パワースペクトルを計算して代入 data[dwDataPos].r = data[i].r * data[i].r + data[i].i * data[i].i; } } //------------------------------------ // おまけ // パワースペクトル計算(C 版) VOID CalcPowerSpec(COMPLEX* data,// FFT を行うデータ DWORD dwFftPoint, // FFT の点数 DWORD dwMaxBit, // dwFftPoint の最大ビット数 double* dCosTable,// Cos のテーブル double* dSinTable// Sin のテーブル ){ // FFT 用変数 DWORD dwStage; DWORD dwPart; DWORD dwDataHeight,dwDataHeight2; // データのポインタ DWORD dwDataPos1,dwDataPos2,dwPoint; // 実数、虚数 double dReal1,dReal2,dReal3,dImag1,dImag2,dImag3; // 雑用 DWORD i,j,k; double dSinn,dCoss; //------------ // 計算部 dwPart = 1; for(dwStage = (DWORD)(log(dwFftPoint)/log(2));dwStage>0;dwStage--) { dwDataHeight = (DWORD)1 << dwStage; // 2^dwStage dwDataHeight2 = dwDataHeight/2; //-------------------------------- // バタフライ演算開始 for(j = 0; j < dwPart ;j++){ dwDataPos1 = dwDataHeight*j; dwDataPos2 = dwDataPos1+dwDataHeight2; for(k = 0 ; k < dwDataHeight2 ; k++){ dReal1 = data[dwDataPos1].r; // 実数 1 dImag1 = data[dwDataPos1].i; // 虚数 1 dReal2 = data[dwDataPos2].r; // 実数 2 dImag2 = data[dwDataPos2].i; // 虚数 2 // 足し合わせ data[dwDataPos1].r = dReal1+dReal2; // 実数の和 data[dwDataPos1].i = dImag1+dImag2; // 虚数の和 //虚数積 dReal3 = dReal1 - dReal2; dImag3 = dImag1 - dImag2; dwPoint = dwStage*dwFftPoint/2+k; dCoss = dCosTable[dwPoint]; dSinn = dSinTable[dwPoint]; data[dwDataPos2].r = dReal3 * dCoss + dImag3 * dSinn; data[dwDataPos2].i = dImag3 * dCoss - dReal3 * dSinn; dwDataPos1++; dwDataPos2++; } } dwPart *=2; // パートを 2 つに分ける } // 計算部ここまで //--------------- //-------------------------------- // 要素入れかえとパワースペクトル計算 for(i = 0; i < dwFftPoint ;i+=2){ //ビットを反転して代入する位置を計算 dwDataPos1 = 0; j = i; for(k=0;k< dwMaxBit;k++){ dwDataPos1 << = 1; dwDataPos1 |= (j & 1); j >>= 1; } // パワースペクトルを計算して代入 data[dwDataPos1].r = data[i].r * data[i].r + data[i].i * data[i].i; } }