ARM での software packed 演算による飽和加算

id:yaneurao:20060916 で、ちょっとアホなコメントしちゃいました。

# melpon 『ARMなら条件実行があるので、素直にif文書いた方が速かったり……。
C言語で書く利点はCPU依存の問題だけでは無いのでアセンブラに頼らないのは賛成ですが、携帯の場合は本気の最適化を求められるので、結局フルアセンブラという罠(;´Д`)
でも ARM 可愛いよ ARM (*´д`*)ハァハァ』

# yaneurao 『> ARMなら条件実行があるので、素直にif文書いた方が速かったり……。

何をするよかifで書くほうが速いの?software packed演算、あれifで書くって言うの?そんなことしたら“当然”遅いよ。ちゃんとベンチとってから言ってよね!(`ω´)』

でもまあ、こんなこと言われたら高速化しないわけにはいかない!とか思って、ちょっといろいろやってみました。


でも、やっぱり software packed演算は偉大でした……。

u_short AddBlend(u_short c0, u_short c1)
{
    u_short c, m;
    c = (((c0 & c1) << 1) + *1 & 0x8420;
    m = c - (c >> 5);
    return (c0 + c1 - c) | m;
}

この関数、ARM アセンブラで考えてみると、

    ; r7 = 0x7bde
    ; r8 = 0x8420
    AND r0, c0, c1          ; c0 & c1
    EOR r1, c0, c1          ; c0 ^ c1
    AND r1, r1, r7          ; (c0 ^ c1) & 0x7bde
    ADD r0, r1, r0, LSL #1  ; ((c0 & c1) << 1) + ((c0 ^ c1) & 0x7bde)
    AND r0, r0, r8          ; c = ((c0 & c1) << 1) + ((c0 ^ c1) & 0x7bde) & 0x8420
    SUB r1, r0, r0, LSR #5  ; m = c - (c >> 5)
    ADD r2, c0, c1          ; c0 + c1
    SUB r2, r2, r0          ; c0 + c1 - c
    ORR r0, r2, r1          ; (c0 + c1 - c) | m

インライン展開と最適化が行われた場合、ARM7 で考えるとわずか9クロック!速すぎですね(;´Д`)


こいつを条件実行で速くしてやろうといろいろやってみたのですが……

u_short AddBlend(u_short c0, u_short c1)
{
    u_short c, m, t;
    t = c0 + c1;
    c = (((c0 & c1) << 1) + *2 & 0x8420;
    if( c != 0 ){
        m = c - (c >> 5);
        t = (t - c) | m;
    }
    return t;
}
    ; r7 = 0x7bde
    ; r8 = 0x8420
    ADD     r3, c0, c1          ; c0 + c1
    AND     r0, c0, c1          ; c0 & c1
    EOR     r1, c0, c1          ; c0 ^ c1
    AND     r1, r1, r7          ; (c0 ^ c1) & 0x7bde
    ADD     r0, r1, r0, LSL #1  ; ((c0 & c1) << 1) + ((c0 ^ c1) & 0x7bde)
    ANDS    r0, r0, r8          ; c = ((c0 & c1) << 1) + ((c0 ^ c1) & 0x7bde) & 0x8420, and flag update
    SUBNE   r1, r0, r0, LSR #5  ; m = c - (c >> 5)
    SUBNE   r3, r3, r0          ; t - c
    ORRNE   r3, r3, r1          ; (t - c) | m

同じだよ!9クロックだよ!


全部1命令だからやっぱりどこかでジャンプさせないといけないんだな……。

u_short AddBlend(u_short c0, u_short c1)
{
    u_short c, m, t;
    t = c0 + c1;
    c = c0 & c1;
    if( c == 0 ) goto end;
    c = ((c << 1) + ((c0 ^ c1) & 0x7bde)) & 0x8420;
    m = c - (c >> 5);
    t = (c0 + c1 - c) | m;
end:
    return t;
}
    ; r7 = 0x7bde
    ; r8 = 0x8420
    ADD     r3, c0, c1          ; c0 + c1
    ANDS    r0, c0, c1          ; c0 & c1, and flag update
    BEQ     end                 ; if( r0 == 0 ) goto end
    EOR     r1, c0, c1          ; c0 ^ c1
    AND     r1, r1, r7          ; (c0 ^ c1) & 0x7bde
    ADD     r0, r1, r0, LSL #1  ; (c << 1) + ((c0 ^ c1) & 0x7bde)
    AND     r0, r0, r8          ; c = (c << 1) + ((c0 ^ c1) & 0x7bde) & 0x8420
    SUB     r1, r0, r0, LSR #5  ; m = c - (c >> 5)
    SUB     r3, r3, r0          ; t - c
    ORR     r3, r3, r1          ; (t - c) | m
end:

c0 & c1 が 0 の時は高速(5クロック)だけど、そうじゃないときは10クロックになってるよ!平均するとむしろ遅くなっちゃうよ!



ダメでしたorz



RGB565 のルーチンなら、マスクの生成にある程度の時間が掛かるみたいなので、条件実行でジャンプすると、もしかしたら速くなるかもしれないですが、RGB555 は厳しいですね(;´Д`)


追記:
RGB565 も高速化出来そうにないですorz

u_short AddBlend(u_short c0, u_short c1)
{
    uint c;
    c = ((c0 & c1) + (((c0 ^ c1) & 0xf7de) >> 1)) & 0x8410;
    c = (((((c + 0xfbdf0)) >> 5) & 0xfbff) + 0x200)^0x7bef;

    return (c0 + c1 - c) | c;
}
    ; r7 = 0xf7de
    ; r8 = 0x8410
    ; r9 = 0xfbdf0
    ; r10 = 0xfbff
    ; r11 = 0x7bef
    AND     r0, c0, c1          ; c0 & c1
    EOR     r1, c0, c1          ; c0 ^ c1
    AND     r1, r1, r7          ; r1 = (c0 ^ c1) & 0xf7de
    ADD     r0, r0, r1, LSR #1  ; r0 = (c0 & c1) + (r1 >> 1)
    AND     r0, r0, r8          ; c = r0 & 0x8410

    ADD     r0, r0, r9          ; c + 0xfbdf0
    AND     r0, r10, r0, LSR #5 ; r0 = (((c + 0xfbdf0)) >> 5) & 0xfbff;
    ADD     r0, r0, #0x200      ; r0 = r0 + 0x200
    EOR     r0, r0, r11         ; r0 = r0 ^ 0x7bef
    ADD     r1, c0, c1          ; c0 + c1
    SUB     r1, r1, r0          ; c0 + c1 - c
    ORR     r0, r1, r0          ; (c0 + c1 - c) | c

空白行以降の命令が12クロック以上の場合は、直前で分岐させてジャンプさせた方が速い可能性が高いのですが……7クロック……マスク生成の演算が速すぎる……。


まあでも、実際は ARM コンパイラじゃ、透過転送での透過色のロードを毎ループやってるぐらいだし、他の処理との兼ね合いもあってレジスタが足りなくなるだろうから、ここまで最適化されたりはしないんだろうなぁ……。
やっぱりどの道、携帯の場合はアセンブラで組むことになるんでしょうね……。

*1:c0 ^ c1) & 0x7bde

*2:c0 ^ c1) & 0x7bde