最近のコンパイラは賢いためかなり最適化されたコードを吐いて くれますが、それでもやはりアセンブラを使った方が(よっぽど間抜けな コードを書かない限り)一般には速くなります。しかし幾つか例外があります。 その一つは 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;
}
}