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