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>までお願いします