neon をいじってみる
Raspberry Pi2 の CPU は SIMD 命令(neon)をサポート
しているので、例題を書いてみた。お題は
"Professional Embedded ARM development"(Wrox)
にあったもので、RGB空間のベタ詰めの画像ファイルを gray
画像に変換する、というものである。変換式はこちら。
- Gray = 0.3*R + 0.59*G + 0.11*B
256倍する。
- 256*Gray = 77*R + 151*G + 28*B
∴ Gray = (77*R + 151*G + 28*B) / 256
掛け算および足し算の時は q レジスタを使用し、画像の
ロード時は d レジスタを使用する。256 で割ることと
右に8ビットシフトすることは同等なので、シフト後に q
レジスタから d レジスタに縮小すれば8ドット分の gray
画像が算出できる。アセンブラで書いてみたのがこちら。
.arch armv7-a .fpu neon-vfpv4 .text .align 2 .global color2gray .type color2gray, %function color2gray: @ color space is R[0]G[0]B[0]R[1]G[1]B[1]... @ r0: base address @ r1: store address @ r2: length of file (not bytes) push {r4-r8} mov r4, #77 mov r5, #151 mov r6, #28 mov r8, r2 vdup.8 d4, r4 vdup.8 d5, r5 vdup.8 d6, r6 lsr r8, r8, #3 movs r8, r8 beq color2gray_boundary color2gray_loop: vld3.8 {d7, d8, d9}, [r0]! vmull.u8 q3, d7, d4 @ Red vmlal.u8 q3, d8, d5 @ Green vmlal.u8 q3, d9, d6 @ Blue vshrn.u16 d3, q3, #3 vst1.8 {d3}, [r1]! subs r8, #1 bne color2gray_loop color2gray_boundary: mov r8, #7 ands r2, r2, r8 beq color2gray_end color2gray_each: ldrb r7, [r0] @ Red mul r8, r4, r7 add r0, r0, #1 ldrb r7, [r0] @ Green mla r8, r5, r7, r8 add r0, r0, #1 ldrb r7, [r0] @ Blue mla r8, r6, r7, r8 lsr r8, r8, #8 strb r8, [r1] subs r2, #1 add r0, r0, #1 add r1, r1, #1 bne color2gray_each color2gray_end: pop {r4-r8} bx lr
C プログラムからは
void color2gray(const unsigned char *rgb, unsigned char *gray, int len);
で呼び出せる。ここで len はバイト数ではなく、
ピクセル数を指定する。
8ピクセルに満たない領域ができた場合は CPU 側で
ループを回すようにした。(color2gray_each の部分)
3.8メガバイトのファイルを処理するのに 11 ミリ秒
くらい要した。
C を使って CPU でループを回すよりだいぶ速い感じ。