«前の日記(2013-10-08 (Tue)) 最新 次の日記(2013-10-14 (Mon))»

ありし日の気分(改)

2002|05|06|07|08|09|10|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|09|10|11|12|
2011|01|02|03|04|05|06|07|10|11|12|
2012|02|03|04|07|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|12|
2015|05|06|
2018|02|03|08|09|12|
2019|10|
2013年
10月
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
昨日のカウンタ:
今日のカウンタ:

[RDF]

最近のトラックバック

2013-10-09 (Wed)

モノクロ演算 (iOSデバイス編) (-Ofast)

先に書いたようにリリースビルドでのデフォルトの最適化オプションは-Osですが、一般的には-Ofastを指定すると更なる最適化を行ってくれます。*1 他の条件は一切変えずに計測したのが以下の結果です。

機種名 iPhone 5s (-Ofast) iPhone 5 (-Ofast) iPhone 4 (-Ofast)
uchar 319 656 2797
uint32 124 320 1335
uint64 249 858 5556
uint16x8 152 1209 3534
float 168 368 1618

ucharで-Osから-Ofastへの差異は少ないのですが、それ以外では最適化の効果が大きく出て高速化されているようです。NEONなどのSIMD処理を使ってループの中の2回〜4回の処理を一度に行っているようです。ではiPhone5sのARMv8の場合にて-Ofastによる最適化がどのように行われているか、ちらっと眺めてみましょう。ループの中の処理だけ抜き出し、邪魔になりそうなコメントは抜いて、あと私の方で最低限のコメントを入れてみました。 コンパイル元のコードについては前の記事を参照して下さい。

  • uchar:
    
    	add	x17, x11, x16  ; x17
    
    	ldrb	w0, [x17]              ; w0 に緑要素(green)を読込
    
    	madd	w0, w0, w13, wzr  ;  w0 := green×150
    
    	ldurb	w1, [x17, #-1]  ; w1に赤要素(red)を読み込み
    
    	madd	w0, w1, w12, w0   ; w0 := red×76 + w0 = red×76 + green×150
    
    	ldrb	w17, [x17, #1]  
    
    	madd	w17, w17, w14, w0 ; w17 := blue×30+w0 = blue×30 + red×76 + green×150
    
    	lsr	w17, w17, #8  ; w17 := w17 >> 8
    
    	add	x0, x10, x16
    
    	add	x16, x16, #4
    
    	strb	w15, [x0, #2]   ; 青要素に書込み
    
    	strb	w17, [x0, #1]   ; 緑要素に書込み
    
    	strb	w17, [x0]   ; 赤要素に書込み
    
    	sturb	w17, [x0, #-1]   ; αチャンネルに書込み
    
    
    びっくりするぐらい最適化してません。C言語ほとんどそのままですね。ちなみにw12, w13, w14, w15に各々即値で76, 150, 30, 0xffが入っています。
  • uint32:
    
    	ldr	q5, [x2], #16    ; 4ピクセル分(128ビット==16バイト)まとめてq5レジスタ(≒v5)に読み込み
    
    	ushr.4s	v4, v5, #16
    
    	and.16b	v4, v4, v0  ; 4バイト毎に16ビット右シフトして 0x000000ff とandしたのをv4
    
    	ushr.4s	v6, v5, #8
    
    	and.16b	v6, v6, v0  ; 4バイト毎に8ビット右シフトして 0x000000ff とandしたのをv6
    
    	mul.4s	v6, v6, v1  ; 4バイトづつ v6 := v6 × 150
    
    	and.16b	v5, v5, v0  ; シフトせず 4ビット毎に 0x000000ff とandしたのをv5に
    
    	mla.4s	v6, v5, v2  ; 4バイトづつ v6 := v6 + v5 × 76
    
    	mla.4s	v6, v4, v3  ; 4バイトづつ v6 := v6 + v4 × 30
    
    	ushr.4s	v4, v6, #8  ; 4バイトづつまとめて右へ8つシフト
    
    	shl.4s	v5, v4, #8
    
    	orr.16b	v5, v4, v5
    
    	shl.4s	v4, v4, #16
    
    	orr.16b	v4, v5, v4  ; 4ピクセル分のモノクロ計算後RGB要素をv4とする
    
    	orr.4s	v4, #255, lsl #24 ; 4ピクセル分のαチャンネルを255とする
    
    	str	q4, [x1], #16   ; 4ピクセル分の計算結果をまとめて格納
    
    	sub	x17, x17, #4
    
    	cbnz	x17, LBB6_5
    
    
    こんな感じで128ビットのNEONレジスタを使って、頼んでもいないのに4ピクセルの計算にまとめていました。しかも最適化後でもシンプル!
  • uint64:
    
    	ldr	q4, [x2], #16  ; 4ピクセル分読み込み
    
    	ushr.2d	v2, v4, #16
    
    	and.16b	v2, v2, v0   ; v2に赤要素(4ピクセル分)
    
    	ushr.2d	v3, v4, #8
    
    	and.16b	v3, v3, v0  ; v3に緑要素(4ピクセル分)
    
    	and.16b	v4, v4, v0  ; v4に青要素(4ピクセル分)
    
    	umov.d	x3, v4[1]
    
    	madd	x3, x3, x15, xzr  ; 青要素の1,2ピクセル目を計算
    
    	fmov	d5, x3
    
    	fmov	x3, d4
    
    	madd	x3, x3, x15, xzr  ; 青要素の3,4ピクセル目を計算
    
    	fmov	d4, x3
    
    	zip1.2d	v4, v4, v5  ; 計算結果を再びv4に格納(4ピクセル分)
    
    	umov.d	x3, v3[1]
    
    	madd	x3, x3, x14, xzr
    
    (長いので以下略)
    
    
    こっちも128ビットのNEONレジスタを使ってくれているのですが、計算が64ビット単位になっているせいで、あまりシンプルになっていません。C言語側で無理して64ビットに2ピクセル分の計算を収めたところが足を引っ張っている印象です。結局2ピクセル単位でしか計算できていませんし…*2
  • uint16×8:
    
    	ldr	d1, [x19, x13]  ; 2ピクセル分(64ビット)まとめて読み込み
    
    	ushll.8h	v1, v1, #0  ; 8個の8ビット数値を8個の16ビット数値に拡張
    
    	mul.8h	v1, v1, v0  ; 各色要素毎に{r,gb}_lum_iを乗算
    
    	ushr.8h	v1, v1, #8  ; 各色要素毎に右に8ビットシフト
    
    	xtn.8b	v1, v1  ; 8個の16ビット数値を8個の8ビット数値に戻す
    
    	fmov	x13, d1
    
    	add	x14, x13, x13, lsr #8
    
    	add	x13, x14, x13, lsr #16
    
    	and	x13, x13, #0xff000000ff  ; モノクロ化後の色要素をx13とする
    
    	madd	x13, x13, x10, xzr  ; 各色要素にモノクロ化した数値を格納
    
    	orr	x13, x13, #0xff000000ff000000  ; αチャンネルを付与
    
    	str	x13, [x0, x11]. ; メモリへ2ピクセル分のデータを格納
    
    	add	x11, x11, #8
    
    	sub	w12, w12, #1
    
    	cbnz	w12, LBB8_3
    
    

    Cで書いたソースそのままです。元が明示的にNEONのこの命令使えというソースからなので、最適化の余地がないのは仕方ないところでしょうか。これが、-Ofastの下ではuint32に負けてしまう理由なのでしょうか。

  • float:
    
    	ldr	q18, [x16], #16  ; 128ビットレジスタに4ピクセル分読み込み
    
    	and.16b	v17, v18, v3
    
    	ucvtf.4s	v17, v17  ; 4ピクセル分の赤要素を4つのfloatへ
    
    	and.16b	v19, v18, v4
    
    	ucvtf.4s	v19, v19  ; 4ピクセル分の緑要素を4つのfloatへ 
    
    	and.16b	v18, v18, v5
    
    	ucvtf.4s	v18, v18  ; 4ピクセル分の青要素を4つのfloatへ
    
    	fmul.4s	v19, v19, v6
    
    	fmla.4s	v19, v7, v17
    
    	fmla.4s	v19, v16, v18  ; 各色要素に{r,g,b}_lum_i を乗算し加算
    
    	fcvtzu.4s	v17, v19  ; 再び4つの整数(unsigned int)に変換
    
    	shl.4s	v18, v17, #8
    
    	orr.16b	v18, v17, v18
    
    	shl.4s	v17, v17, #16
    
    	orr.16b	v17, v18, v17  ; 4ピクセル分のモノクロ値をRGBに設定
    
    	orr.4s	v17, #255, lsl #24  ; 4ピクセル分のαチャンネルを設定
    
    	str	q17, [x15], #16
    
    	sub	x14, x14, #4
    
    	cbnz	x14, LBB9_5
    
    

    読みやすいです。そしてNEONを使った並列計算をうまく生かせた最適化になっています。これにより浮動小数点演算でありながら最速のuint32に迫る速度をiPhone5s/iPhone5/iPhone4のいずれでも出せています。

iPhone5sでの事前の予測としてはuint64かuint16x8が-Ofast付けても高速かなーと思っていたのですが、uint32が最速となりました。最適化後のアセンブリコードを眺めてみると、いずれもNEONをしっかり使ったものになっています。

同じNEONを使ってもuint16x8では一つの128ビットレジスタでの2ピクセル同時計算に留まらせた一方で、uint32では色要素毎の3つの128ビットレジスタを使って4ピクセル同時計算にまで最適化できているのが大きな違いでしょうか。

うまく最適化を生かせるCコードを書けば、素人の下手な最適化(uint16x8)に比べてより良い最適化(uint32)をしてくれるというのは嬉しいところです。また浮動小数点演算でもうまく最適化にはまれば良いコードを出してくれるのも嬉しいところです。

*1 ちなにみにAndroiod NDKでの最適化デフォルト値は-O2のようですが、先の実験では-Ofastを指定しています。

*2 もっとうまい最適化できるんじゃないかとか思ってしまったりもしますが(苦笑)



C++でつくるUnreal Engineアプリ開発 for Windows & macOS  UE4でC++を使う方法を書いた本です。

«前の日記(2013-10-08 (Tue)) 最新 次の日記(2013-10-14 (Mon))»


2002|05|06|07|08|09|10|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|09|10|11|12|
2011|01|02|03|04|05|06|07|10|11|12|
2012|02|03|04|07|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|12|
2015|05|06|
2018|02|03|08|09|12|
2019|10|