240 likes | 364 Views
小寺春樹. Hack the Cell 2009 反省会の反省. に書いておきます。. 口頭で説明した概要を. バイナリアン風味. Z80,68k,SH2,R3000, R4300,ARM7,PowerPC. ※なにはともあれ objdump. 色々なCPUを使ってきました。 Cellはまぁ、今回位では使った内には入らないですね。. ビット転置(スライス)実装. 基本的な事はおいておいて、変わってるところ. レジスタ幅を一杯に使って演算(実は致命傷). 623循環で組まなかった言い訳. 最初期の簡略化で思考固定化. 状態空間を 2^n サイズにして、
E N D
小寺春樹 Hack the Cell 2009反省会の反省 に書いておきます。 口頭で説明した概要を
バイナリアン風味 Z80,68k,SH2,R3000, R4300,ARM7,PowerPC ※なにはともあれ objdump 色々なCPUを使ってきました。 Cellはまぁ、今回位では使った内には入らないですね。
ビット転置(スライス)実装 基本的な事はおいておいて、変わってるところ レジスタ幅を一杯に使って演算(実は致命傷) 623循環で組まなかった言い訳
最初期の簡略化で思考固定化 状態空間を 2^n サイズにして、 EN = 1023; // > N y = (mt[ idx & EN ]&UPPER_MASK) | (mt[ (idx+1)& EN ] & LOWER_MASK); y = mt[ (idx+M)& EN ] ^ (y >> 1) ^ ((y&1)?MATRIX_A:0); mt[ (idx+N)&EN ] = y; インデクス計算時の mod 624 処理、idx == N での比較分岐なりが削れる。 mt 状態空間サイズは ・624ワード以上の任意 ・コアループ内の処理数もそれに応じて適当に変動 全部アンロールするとか考えてなかった時は、2^nに 状態空間拡張するのが単純でそこそこ速い。
レジスタ幅をいっぱいに使わないと? 処理単位はレジスタサイズの128bit 状態空間サイズが624(623+1)だと、端数が出る 処理単位毎の処理が均一じゃなくなる ループ当たり処理要素数は少なくとも 624/640 になる。 コードサイズがでかくなる(後述) ビットスライス実装にしても2^nの方がレジスタにはまるし、 なんとなく効率良いと思っちゃってそれっきり。
0 396 623 +1 +M 時間 状態空間更新のイメージ 赤横線が読み出しで、計算して、青に書き込む感じ。
0 396 623 +1 +M 時間 実際は矢印部分はレジスタで引き渡しますので、 メモリのロード&ストアはライン当たり32ずつで済んでます。
0 396 623 +1 +M 時間 に割り当てられるレジスタが1ライン毎に変わる -> コアループは偶数ラインで構成
even/odd のバランス調整 基本的にevenが重いので、ステップ数増加を抑えつつ odd側に振れる仕事を振っていきます。
0 396 623 +1 +M 時間 +Mの値を取得するところ、2つのレジスタを結合して 回す処理のところで調整を行います。
even*1,odd*2 odd*4 A B A B shufb dst,A,B,shufc1 sel dst,A,B,mask B A = B A rotqbii tmp,B,4 B B rotqbii dst,dst,4 B B A rotqbyi dst,dst,#14 rotqbii dst,dst,#4 A B shufb dst,dst,tmp,shufc2 even-1,odd+2となる代替処理の説明。 なんとなく出来る事が分かって 貰えればいいかなぁ。 A B
tempering temperingも一応考えました
n=[] def eor(a,b) puts "(#{a}^#{b})" "(#{a}^#{b})" end 0.upto(31){|i| n[i]="n#{i}" } 31.downto(11){|i| n[i]=eor(n[i],n[i-11]) } [0,3,4,5,7,10,12,13,17,19,21,22,24].each{|i| n[i] = eor(n[i],n[i+7]) } [0,1,2,4,5,6,7,8,9,13,14].each{|i| n[i] = eor(n[i],n[i+15]) } 31.downto(18){|i| n[i] = eor(n[i],n[i-18]) } こんなRubyスクリプトを書いて、各ビットに元ビットがどう 反映されているかを調べます。
(n31^n20) (n30^n19) (n29^n18) (n28^n17) (n27^n16) (n26^n15) (n25^n14) (n24^n13) (n23^n12) (n22^n11) (n21^n10) (n20^n9) (n19^n8) (n18^n7) (n17^n6) (n16^n5) (n15^n4) (n14^n3) (n13^n2) (n12^n1) (n11^n0) (n0^n7) (n3^n10) (n4^(n11^n0)) (n5^(n12^n1)) (n7^(n14^n3)) (n10^(n17^n6)) ((n12^n1)^(n19^n8)) ((n13^n2)^(n20^n9)) ((n17^n6)^(n24^n13)) ((n19^n8)^(n26^n15)) ((n21^n10)^(n28^n17)) ((n22^n11)^(n29^n18)) ((n24^n13)^(n31^n20)) ((n0^n7)^(n15^n4)) (n1^(n16^n5)) (n2^((n17^n6)^(n24^n13))) ((n4^(n11^n0))^((n19^n8)^(n26^n15))) ((n5^(n12^n1))^(n20^n9)) (n6^((n21^n10)^(n28^n17))) ((n7^(n14^n3))^((n22^n11)^(n29^n18))) (n8^(n23^n12)) (n9^((n24^n13)^(n31^n20))) (((n13^n2)^(n20^n9))^(n28^n17)) ((n14^n3)^(n29^n18)) ((n31^n20)^(((n13^n2)^(n20^n9))^(n28^n17))) ((n30^n19)^((n12^n1)^(n19^n8))) ((n29^n18)^(n11^n0)) ((n28^n17)^(n10^(n17^n6))) ((n27^n16)^(n9^((n24^n13)^(n31^n20)))) ((n26^n15)^(n8^(n23^n12))) ((n25^n14)^((n7^(n14^n3))^((n22^n11)^(n29^n18)))) (((n24^n13)^(n31^n20))^(n6^((n21^n10)^(n28^n17)))) ((n23^n12)^((n5^(n12^n1))^(n20^n9))) (((n22^n11)^(n29^n18))^((n4^(n11^n0))^((n19^n8)^(n26^n15)))) (((n21^n10)^(n28^n17))^(n3^n10)) ((n20^n9)^(n2^((n17^n6)^(n24^n13)))) (((n19^n8)^(n26^n15))^(n1^(n16^n5))) ((n18^n7)^((n0^n7)^(n15^n4))) 出力はこんな感じ。一番左側がターゲットビット。 これをよく睨むと…
(n31^n20) (n30^n19) (n29^n18) (n28^n17) (n27^n16) (n26^n15) (n25^n14) (n24^n13) (n23^n12) (n22^n11) (n21^n10) (n20^n9) (n19^n8) (n18^n7) (n17^n6) (n16^n5) (n15^n4) (n14^n3) (n13^n2) (n12^n1) (n11^n0) (n0^n7) (n3^n10) (n4^(n11^n0)) (n5^(n12^n1)) (n7^(n14^n3)) (n10^(n17^n6)) ((n12^n1)^(n19^n8)) ((n13^n2)^(n20^n9)) ((n17^n6)^(n24^n13)) ((n19^n8)^(n26^n15)) ((n21^n10)^(n28^n17)) ((n22^n11)^(n29^n18)) ((n24^n13)^(n31^n20)) ((n0^n7)^(n15^n4)) (n1^(n16^n5)) (n2^((n17^n6)^(n24^n13))) ((n4^(n11^n0))^((n19^n8)^(n26^n15))) ((n5^(n12^n1))^(n20^n9)) (n6^((n21^n10)^(n28^n17))) ((n7^(n14^n3))^((n22^n11)^(n29^n18))) (n8^(n23^n12)) (n9^((n24^n13)^(n31^n20))) (((n13^n2)^(n20^n9))^(n28^n17)) ((n14^n3)^(n29^n18)) ((n31^n20)^(((n13^n2)^(n20^n9))^(n28^n17))) ((n30^n19)^((n12^n1)^(n19^n8))) ((n29^n18)^(n11^n0)) ((n28^n17)^(n10^(n17^n6))) ((n27^n16)^(n9^((n24^n13)^(n31^n20)))) ((n26^n15)^(n8^(n23^n12))) ((n25^n14)^((n7^(n14^n3))^((n22^n11)^(n29^n18)))) (((n24^n13)^(n31^n20))^(n6^((n21^n10)^(n28^n17)))) ((n23^n12)^((n5^(n12^n1))^(n20^n9))) (((n22^n11)^(n29^n18))^((n4^(n11^n0))^((n19^n8)^(n26^n15)))) (((n21^n10)^(n28^n17))^(n3^n10)) ((n20^n9)^(n2^((n17^n6)^(n24^n13)))) (((n19^n8)^(n26^n15))^(n1^(n16^n5))) ((n18^n7)^((n0^n7)^(n15^n4))) この辺なんか無駄っぽい。(n18^n7)は最後にしか出て来ないのに、 n7は結局打ち消されてたり。
(n31^n20) (n30^n19) (n29^n18) (n28^n17) (n27^n16) (n26^n15) (n25^n14) (n24^n13) (n23^n12) (n22^n11) (n21^n10) (n20^n9) (n19^n8) (n18^n7)->x (n17^n6) (n16^n5) (n15^n4) (n14^n3) (n13^n2) (n12^n1) (n11^n0) (n0^n7)->x (n3^n10) (n4^(n11^n0)) (n5^(n12^n1)) (n7^(n14^n3)) (n10^(n17^n6)) ((n12^n1)^(n19^n8)) ((n13^n2)^(n20^n9)) ((n17^n6)^(n24^n13)) ((n19^n8)^(n26^n15)) ((n21^n10)^(n28^n17)) ((n22^n11)^(n29^n18)) ((n24^n13)^(n31^n20)) ((n0^n7)^(n15^n4)) -> (n0^(n15^n4)) , ((n0^(n15^n4))^n7) (n1^(n16^n5)) (n2^((n17^n6)^(n24^n13))) ((n4^(n11^n0))^((n19^n8)^(n26^n15))) ((n5^(n12^n1))^(n20^n9)) (n6^((n21^n10)^(n28^n17))) ((n7^(n14^n3))^((n22^n11)^(n29^n18))) (n8^(n23^n12)) (n9^((n24^n13)^(n31^n20))) (((n13^n2)^(n20^n9))^(n28^n17)) ((n14^n3)^(n29^n18)) ((n31^n20)^(((n13^n2)^(n20^n9))^(n28^n17))) ((n30^n19)^((n12^n1)^(n19^n8))) ((n29^n18)^(n11^n0)) ((n28^n17)^(n10^(n17^n6))) ((n27^n16)^(n9^((n24^n13)^(n31^n20)))) ((n26^n15)^(n8^(n23^n12))) ((n25^n14)^((n7^(n14^n3))^((n22^n11)^(n29^n18)))) (((n24^n13)^(n31^n20))^(n6^((n21^n10)^(n28^n17)))) ((n23^n12)^((n5^(n12^n1))^(n20^n9))) (((n22^n11)^(n29^n18))^((n4^(n11^n0))^((n19^n8)^(n26^n15)))) (((n21^n10)^(n28^n17))^(n3^n10)) ((n20^n9)^(n2^((n17^n6)^(n24^n13)))) (((n19^n8)^(n26^n15))^(n1^(n16^n5))) ((n18^n7)^((n0^n7)^(n15^n4))) -> (n18^(n0^(n15^n4))) こう変形すると、1ステップだけ減ります。 これ以上減らないの?わかりません。
e cntb $r14,$r14 e cntb $r30,$r30 e sumb $r14,$r30,$r14 r $r30 e cntb $r15,$r15 e cntb $r31,$r31 e sumb $r15,$r31,$r15 r $r31 o shufb $r0,$r0,$r8,shufc r $r8 e a %0,%0,$r0 r $r0 ・発行順依存関係調べます ・発行可能な中では単純に先着優先。あとはベースコード側の工夫でなんとかします。 ・r っていうのはレジスタのリリース。まぁ、自動化するまでもないかなぁ、と。
レジスタの動的割り当てによる再利用 参照完了直後に再利用。 999ステップ目等はdual issue で参照完了と同時にターゲットになってたり。
結果加算は8本の累積レジスタで処理 0.upto(15){ |x| i = (x&1)*8+(x/2) @asm<<"e cntb $r#{i},$r#{i}" @asm<<"e cntb $r#{i+16},$r#{i+16}" @asm<<"e sumb $r#{i},$r#{i+16},$r#{i}" } 0.upto(7){|i| @asm<<"o shufb $r#{i},$r#{i},$r#{i+8},shufc" @asm<<"e a %#{i},%#{i},$r#{i}" } ※shufc = {1,17,3,19, 5,21,7,23, 9,25,11,27, 13,29,15,31} ライン当たりコスト: cntb*32, sumb*16, shufb*8, a*8 even56,odd8と、合計ステップ数64でもoddに8振れているのがポイント
例)bit0,8,16,24の集計 cntb cntb bit8 bit24 + + sumb bit0,8,16,24 累積レジスタ shufb a sumb + + cntb bit0 cntb bit16 8ビット間隔の重みのデータをcntb,sumb,shufbで組み上げる事で、 SIMDワードとしてそのまま加算出来ますね、という話なのですが これもあまり説明うまくなかったですね。 ※bit0 = MSB
その他 MT状態空間の3/32をレジスタに常駐 連続メモリアクセスストールの回避 端数の処理もSIMD化している 1回限りのビット転置も高速化している カウンタをoddパイプで実装 実行後のmtは問われないから、端数の処理を先にやる様に するとビット転置は1回で済んで楽ですよね。ん、そうでもない? rotqbiiで7bitカウンタを作る話