HackTheCellPostMortem
HackTheCellPostMortemお疲れ様でした。id:shinichiro_h さん、fixstars の皆様、ありがとうございました。
いやー、楽しかったです。勉強会の方は blog 読むだけじゃよく解らなくて流してたところとかもちゃんと理解できて、久々に脳が活性化した気がします。宴会もめっさ楽しかったですねぇ。がっつりエンジニアで呑む機会も、異業種の人と話す機会も、若くて凄い人たちと接する機会も全然ないので相当楽しかったっす。もっと動いてもっと色んな人と話すべきだったなぁ。
あ、勉強会の頭の方では自分がどんなコード書いたのか全然覚えてなくて色々嘘言ってました orz。 asm 化作業もしてたんで asm で書いたって言ったけど、よく見たら最後の動くコードは思いっきり c でした(ぉぃ) あと、loop に 2 ROUND 分展開した人居る?ってのにも返事しなかったですけど、思いっきり展開してましたw
http://d.hatena.ne.jp/spu/20090330/1238423874
一応貼ってみたけど、shinh さんの云う通り「ああ俺はどうしようもなかったんだな…」というのを再確認する以外の意味がないような…orz ってか、根本的に spu-gcc 4.3 じゃないんで意味ないだろって気もするしw とはいえ、恥をかく度胸も必要な気がするので貼ってしまえ!!ってことで。
いやー、とにかく楽しかったんで、来年あれば是非また挑戦したいと思います!!(していいのかは置いといてw)
# それはそうと、これを機にはてなブログを始めてみたんだけど、こんなIDが余ってたんで取得しちまいましたw
Hack the Cell 2009 感想
感想は『みんなすげー』に尽きてしまう。こういうコンテスト初だったけど、めちゃめちゃ楽しめました。って、出してないんだから何言ってんだって感じだけど orz
http://d.hatena.ne.jp/kikx/20090310#1236700899
結局、bitslice を実装しなかった時点で終わってるんだけど、そのまま続けていたとしても、ここまでは出来ないよなぁ〜。id:kikx さん凄すぎです。
http://d.hatena.ne.jp/kikx/20090306#1236358887
でも実は、bitslice でないこちらの最適化の方が衝撃でした。脱帽です。コード弄ってるのに終始するんじゃなくて、いったん引いて俯瞰で全体を見ないと、って感じなのかな…
なんか、自分が思いつきそうな範疇の外側を行かれると、悔しいってより清々しいっすねw
とりあえず貼ってみる
Hack the Cell 2009、提出しなかったけど(ぉぃ)とりあえずやったので貼ってみる。
/* Copyright (C) 2009, A.H. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <stdio.h> #include <spu_mfcio.h> #include "mt_mine.h" /* Period parameters */ #define N 624 #define M 397 #define NV (N/4) #define MV (M/4) #define NA (NV*16) #define MA (MV*16) #define MATRIX_A 0x9908b0dfUL /* constant vector a */ #define UPPER_MASK 0x80000000UL /* most significant w-r bits */ #define LOWER_MASK 0x7fffffffUL /* least significant r bits */ typedef signed char int8_t; typedef unsigned char uint8_t; typedef signed int int32_t; typedef unsigned int uint32_t; typedef vector signed char int8v_t; typedef vector unsigned char uint8v_t; typedef vector signed int int32v_t; typedef vector unsigned int uint32v_t; // 端数処理の際の index 処理を簡素化するため、2倍の領域を利用 static uint32_t mt[N*2] __attribute__((aligned(16))); static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */ /* initializes mt[N] with a seed */ void init_genrand_mine(unsigned long s) { mt[0]= s & 0xffffffffUL; for (mti=1; mti<N; mti++) { mt[mti] = (1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti); /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ /* In the previous versions, MSBs of the seed affect */ /* only MSBs of the array mt[]. */ /* 2002/01/09 modified by Makoto Matsumoto */ mt[mti] &= 0xffffffffUL; /* for >32 bit machines */ } } uint32_t matrixes[] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x9908b0df, 0x00000000, 0x00000000, 0x9908b0df, 0x00000000, 0x00000000, 0x00000000, 0x9908b0df, 0x9908b0df, 0x00000000, 0x9908b0df, 0x00000000, 0x00000000, 0x00000000, 0x9908b0df, 0x00000000, 0x9908b0df, 0x00000000, 0x9908b0df, 0x9908b0df, 0x00000000, 0x00000000, 0x9908b0df, 0x9908b0df, 0x9908b0df, 0x9908b0df, 0x00000000, 0x00000000, 0x00000000, 0x9908b0df, 0x00000000, 0x00000000, 0x9908b0df, 0x9908b0df, 0x00000000, 0x9908b0df, 0x00000000, 0x9908b0df, 0x00000000, 0x9908b0df, 0x9908b0df, 0x9908b0df, 0x9908b0df, 0x00000000, 0x00000000, 0x9908b0df, 0x9908b0df, 0x00000000, 0x9908b0df, 0x9908b0df, 0x9908b0df, 0x9908b0df, 0x00000000, 0x9908b0df, 0x9908b0df, 0x9908b0df, 0x9908b0df, }; // global 変数にすることで最適化を抑制 // static 化するとレジスタを消費するため遅くなる uint32v_t *vmt = (uint32v_t*)mt; const uint32v_t *cvmt = (uint32v_t*)mt; // static 変数にすることで最適化(初期化用) static uint32v_t *const svmt = (uint32v_t*)mt; #define NL 7 // 分割ステップ数 #define ND 3 // STEP01 から STEP04 までの遅延分 #define NP 3 // 事前処理ステップ数 #define NF (NP-ND) // loop 初回の table offset #define NE (NL-NP) // 事後処理ステップ数 #define NDA (ND*16) // STEP01F での table offset #define NIDX (-NA+(NF*16)) // STEP01F/04F 用 index 初期値 #define spu_lqx(ra,rb) *(uint32v_t*)( ra + (int32_t)spu_extract( rb, 0 ) ) #define expect(flag,dir) __builtin_expect( (flag), dir ) #define likely(flag) __builtin_expect( (flag), 1 ) #define unlikely(flag) __builtin_expect( (flag), 0 ) unsigned int genrand_mine( int num_rand ) { uint8_t * const mats = (uint8_t*)matrixes; uint8_t * const mts = (uint8_t*)mt + NA; uint8_t * const mte = (uint8_t*)mt + NA + NA; uint8_t * const mt1 = (uint8_t*)mt + NA + NDA; uint8_t * const mtM = (uint8_t*)mt + NA + MA + NDA; const uint32v_t zero = { 0 }; const uint32v_t y07_mask = spu_splats( (uint32_t)0x9d2c5680 ); const uint32v_t y15_mask = spu_splats( (uint32_t)0xefc60000 ); const uint32v_t upper_mask = spu_splats( (uint32_t)0x80000000 ); const uint8v_t ptn = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; volatile int t = 0; // 最適化抑制用 uint32v_t idx = spu_splats( (uint32_t)(NIDX) ); // STEP01F/04F での table index uint32v_t kk1a, kkMa, kv1a, kvMa, rlma, ygba, adra, maga, suma; uint32v_t kk1b, kkMb, kv1b, kvMb, rlmb, ygbb, adrb, magb, sumb; uint32v_t y01a, y02a, y03a, y04a, y05a, y06a, y07a, y08a, y09a, y10a, y11a, y12a, y13a, y14a; uint32v_t y01b, y02b, y03b, y04b, y05b, y06b, y07b, y08b, y09b, y10b, y11b, y12b, y13b, y14b; kk1a = kk1b = svmt[0]; kkMa = kkMb = svmt[MV]; y01a = y02a = y03a = y04a = y05a = y06a = y07a = zero; y01b = y02b = y03b = y04b = y05b = y06b = y07b = zero; y08a = y09a = y10a = y11a = y12a = y13a = y14a = suma = zero; y08b = y09b = y10b = y11b = y12b = y13b = y14b = sumb = zero; // SIMD 化した更新処理(13cycle)を 7step に分割 (step 間の変数依存を調整) // 逆順ループ(07 to 01)を7回実行すると答えが出るようにし最適化を推進 // 変数を x/y 2面持つことで 2 ROUND 間でも依存関係をなくし最適化を推進 # define STEP01(x,y,n) \ kk1##x = cvmt[ ( (n) + 1 + NP ) % NV ]; \ kkM##x = cvmt[ ( (n) + 1 + NP + MV ) % NV ]; \ kv1##x = spu_shuffle( kk1##y, kk1##x, ptn ); \ kvM##x = spu_shuffle( kkM##y, kkM##x, ptn ); \ # define STEP02(x,y) \ y01##x = spu_sel( kv1##x, kk1##y, upper_mask ); \ ygb##x = spu_gather( kv1##x ); \ rlm##x = spu_rlmask( y01##x, -1 ); \ adr##x = spu_slqw( ygb##x, 4 ); \ y02##x = spu_xor( rlm##x, kvM##x ); \ # define STEP03(x) \ mag##x = spu_lqx( mats, adr##x ); \ y03##x = spu_xor( y02##x, mag##x ); \ # define STEP04(x,n) \ y04##x = spu_rlmask( y03##x, -11 ); \ y05##x = spu_xor( y03##x, y04##x ); \ y06##x = spu_slqw( y05##x, 7 ); \ vmt[ ( (n) + NF ) % NV ] = y03##x; \ # define STEP05(x) \ y07##x = spu_and( y06##x, y07_mask ); \ y08##x = spu_xor( y05##x, y07##x ); \ y09##x = spu_slqw( y08##x, 15 ); \ # define STEP06(x) \ y10##x = spu_slqwbytebc( y09##x, 15 ); \ y11##x = spu_and( y10##x, y15_mask ); \ y12##x = spu_xor( y08##x, y11##x ); \ # define STEP07(x,y) \ y13##x = spu_rlmask( y12##x, -18 ); \ y14##x = spu_xor( y12##x, y13##x ); \ sum##x = spu_add( sum##y, y14##x ); \ // 上記 STEP01/04 の固定値による table access を変数参照化 # define STEP01F(x,y) \ idx = spu_add( idx, 16 ); \ kk1##x = *(uint32v_t*)( mt1 + (int32_t)spu_extract( idx, 0 ) ); \ kkM##x = *(uint32v_t*)( mtM + (int32_t)spu_extract( idx, 0 ) ); \ kv1##x = spu_shuffle( kk1##y, kk1##x, ptn ); \ kvM##x = spu_shuffle( kkM##y, kkM##x, ptn ); \ # define STEP04F(x) \ y04##x = spu_rlmask( y03##x, -11 ); \ y05##x = spu_xor( y03##x, y04##x ); \ y06##x = spu_slqw( y05##x, 7 ); \ *(uint32v_t*)( mts + (int32_t)spu_extract( idx, 0 ) ) = y03##x; \ *(uint32v_t*)( mte + (int32_t)spu_extract( idx, 0 ) ) = y03##x; \ // 事前処理する部分をマクロ化 (3step) # define PROLOGUE \ STEP01(b,a,-NP+0) \ STEP02(b,a) STEP01(a,b,-NP+1) \ STEP03(b) STEP02(a,b) STEP01(b,a,-NP+2) \ // 各 STEP を逆順に実行することにより、依存関係なく最適化 # define ROUNDa(n) \ { STEP07(a,b) STEP06(b) STEP05(a) STEP04(b,n) STEP03(a) STEP02(b,a) STEP01(a,b,n) } # define ROUNDb(n) \ { STEP07(b,a) STEP06(a) STEP05(b) STEP04(a,n) STEP03(b) STEP02(a,b) STEP01(b,a,n) } // 端数処理のための変数参照による更新処理 # define FRACTIONa \ { STEP07(a,b) STEP06(b) STEP05(a) STEP04F(b) STEP03(a) STEP02(b,a) STEP01F(a,b) } # define FRACTIONb \ { STEP07(b,a) STEP06(a) STEP05(b) STEP04F(a) STEP03(b) STEP02(a,b) STEP01F(b,a) } // 最適化抑制のため、volatile 変数を参照した branch // ※ spu-gcc 4.1.1 にて。他の version の場合は他の回避策で。 # define RBREAK { if ( unlikely( t ) ) break; } // mt[624] の更新処理をまとめたマクロ (最終段のみ最適化抑制コードを除外) # define ROUND2(n) { ROUNDa((n)*2+0) ROUNDb((n)*2+1) } # define ROUND2R(n) { ROUND2(n) RBREAK } # define ROUND2L(n) { ROUND2(n) } # define ROUND6R(n) { ROUND2R((n)*3+0) ROUND2R((n)*3+1) ROUND2R((n)*3+2) } # define ROUND6L(n) { ROUND2R((n)*3+0) ROUND2R((n)*3+1) ROUND2L((n)*3+2) } # define R(n) { ROUND6R((n)*2+0) ROUND6R((n)*2+1) } # define L(n) { ROUND6R((n)*2+0) ROUND6L((n)*2+1) } # define ROUNDS { R(0) R(1) R(2) R(3) R(4) R(5) R(6) R(7) R(8) R(9) R(10) R(11) L(12) } num_rand = ( num_rand >> 2 ) + NE; // SIMD 化、事後処理分を調整 uint32_t num_main = num_rand / NV; uint32_t num_remain = num_rand % NV; PROLOGUE // main loop. 奇数回の場合は真ん中で break. uint32v_t count = spu_splats( num_main ); do { ROUNDS uint32v_t check = spu_rlmaskqw( count, -1 ); if ( unlikely( !spu_extract( check, 0 ) ) ) break; ROUNDS count = spu_add( count, -2 ); } while ( likely( spu_extract( count, 0 ) ) ); // 端数処理。4個単位で処理し、break と事後処理で結果を取捨選択 count = spu_splats( num_remain >> 1 ); while ( likely( spu_extract( count, 0 ) ) ) { FRACTIONa FRACTIONb uint32v_t check = spu_rlmaskqw( count, -1 ); if ( unlikely( !( spu_extract( check, 0 ) ) ) ) break; FRACTIONa FRACTIONb count = spu_add( count, -2 ); } uint32v_t sumv = !( num_remain & 1 ) ? suma : sumb; // SIMD 化した4要素を加算 unsigned int sum = spu_extract( sumv, 0 ); sum += spu_extract( sumv, 1 ); sum += spu_extract( sumv, 2 ); sum += spu_extract( sumv, 3 ); return sum; }
貼ってはみたものの…
変な環境で作ってたので、spu-gcc 4.3 ではどうなるのか分らなかったりする。。
あとで asm 化しながら 4.3 対応すれば良いや、と思いつつ、結局2月以降は時間が取れなくてリタイア。ってかその頃、bitslice に気付くと同時に、それを実装する気力も時間も持ち合わせていないことにも気付いてしまった orz
一応 spu-gcc 4.1.1 で作って実行した場合は、
ORIGNAL: sum=3c927c56, 218149723 ticks MINE: sum=3c927c56, 4726379 ticks ORIGNAL: sum=2e987a4d, 314693214 ticks MINE: sum=2e987a4d, 6818068 ticks ORIGNAL: sum=ef1b6aef, 231558068 ticks MINE: sum=ef1b6aef, 5016892 ticks ORIGNAL: sum=eedd2516, 215200058 ticks MINE: sum=eedd2516, 4662468 ticks ORIGNAL: sum=f7e967a8, 10659118 ticks MINE: sum=f7e967a8, 230948 ticks ORIGNAL: sum=1f37a7db, 158933062 ticks MINE: sum=1f37a7db, 3443407 ticks ORIGNAL: sum=c7d41f36, 218842322 ticks MINE: sum=c7d41f36, 4741382 ticks ORIGNAL: sum=aa9d2e9f, 192578829 ticks MINE: sum=aa9d2e9f, 4172378 ticks ORIGNAL: sum=8abd398a, 186108495 ticks MINE: sum=8abd398a, 4032186 ticks ORIGNAL: sum=a374bd58, 4533322 ticks MINE: sum=a374bd58, 98225 ticks
ってな結果でした。ROUND 毎に最適化抑制してるようなコードなんで、asm 化するのはそんなに大変ではないかなぁ、と。ってか一応やりかけたんだけど、その気力も bitslice に奪われたかな〜 orz
今となっては恥以外の何物でもない気もするけど、自戒の意を込めて公開〜