K6向けの最適化
ここではK6で組む時に気をつけることをいくつか書いていきます.
次のサンプルを見てみます.
int ix_max( int *ix, unsigned int begin, unsigned int end ){
int i, x, max = 0;
for ( i = begin; i < end; i++ ){
x = ix[i];
if( x > max )
max = x;
}
return max;
}
VC++6.0では次のコードを生成しました.
_ix$ = 8
_begin$ = 12
_end$ = 16
mov edx, DWORD PTR _begin$[esp-4]
mov ecx, DWORD PTR _end$[esp-4]
xor eax, eax
cmp edx, ecx
jae SHORT $L2042
push esi
mov esi, DWORD PTR _ix$[esp]
sub ecx, edx
lea esi, DWORD PTR [esi+edx*4]
$L2040:
mov edx, DWORD PTR [esi]
cmp edx, eax
jle SHORT $L2041
mov eax, edx
$L2041:
add esi, 4
dec ecx
jne SHORT $L2040
pop esi
$L2042:
ret 0
begin=0, end=576の時, 約3500clkで動作しました. 一見, 問題ないコードに見えますが, K6で実行する場合には欠陥があります. それはアドレッシングに[esi]を使っているということです. このペナルティは2clkもかかります. その証拠に上のコードのesiをediに置き換えるだけで約2400clkまで速度アップしました. 何も知らなかったら[esi]を使ってしまうでしょう. コンパイラが[esi]を使うことが多いだろうと思うとこれは結構痛い欠点だと思います.
どうしても[esi]を使わざるを得ない場合は, [esi+0]というアドレッシングにすれば回避できます. ただ通常のアセンブラは[esi+0]と記述しても[esi]と見なすので, 自分でマシンコードを書く必要があります.
更にdec+jneの部分をloop命令にするだけで約1800clkとなります. Intel系CPUではloop命令は遅い命令の代表的なものでしたが, K6-2ではめちゃくちゃ速いです. K6-2でもdec+jnzは速いのですが, loop命令の方がaluを使わない分ペアリング率があがるのかもしれません.
しばらく前, K6-2 350MHzを使うと95が起動しなくなるという問題がありました. あれはソフトウエアタイマにloop $を使っていたため, あまりに速く実行されてしまったのが原因です.
尚, このプログラムは3D Now!のpfmaxをうまく使うと約1000clkまで高速化出来ます. ただこの場合注意しないといけないのは単精度小数では24bit精度つまり,絶対値が16777216以下の整数までしか厳密に扱えないということです. それ以下の整数しか扱わないのならpi2fdによる情報落ちはありませんが, 使って大丈夫かどうか事前に調べておく必要があります.
御意見は光成滋生<herumi@nifty.com>までお願いします