■
https://github.com/tanakamura/waifu2x-converter-cpp/tree/simd_improve
やる気が出てきたのでチューニングしなおした。(バイナリは http://int.main.jp/files/waifu2x-converter_x64_1101.zip )
cpu | before[GFLOPS] | after[GFLOPS] | improvements[%] | efficiency[%] | theoretical peak[GFLOPS] | 備考 | |||||||
i7-6700(Skylake TB無し3.4G固定 4C8T) | 259.3 | 359.8 | 38.8 | 82.7 | 435.2 | ||||||||
Tegra4(Cortex-A15 1.6GHz x 4) | 11.3 | 21.2 | 87.6 | 41.2 | 51.392 | クロックは変動大きいのであやしい | |||||||
Snapdragon 410(Cortex-A53 1.21GHz x4) | 6.37 | 9.46 | 48.5 | 24.4 | 38.72 | ||||||||
Celeron 2955U(Haswell 1.4GHz 2C SSE) | 16.7 | 17.5 | 4.7 | 78.1 | 22.4 | ||||||||
i7-4700MQ(Haswell TB無し2.4GHz固定 4C8T) | 145.0 | 245.0 | 63.3 | 79.8 | 307.2 | ||||||||
BCM2836(Cortex-A7x2 900MHz) | 2.41 | 3.78 | 56.8 | 52.5 | 7.2 | ||||||||
Core2 Duo 1.4GHz | 11.2 | 12.3 | 9.8 | 54.9 | 22.4 | ||||||||
MT8173(Cortex-A72x2 1.989GHz 32bit) | 13.3 | 18.4 | 38.3 | 57.8 | 31.824 | 周波数はCPU-Z目視 | |||||||
MT8173(Cortex-A72x2 1.989GHz 64bit) | 15.8 | 15.2 | -3.8 | 47.8 | 31.824 | 周波数はCPU-Z目視 |
性能は、ファイル入出力を含まない(runbenchの値)
- ちゃんと最内ループがキャッシュに入るようにした
- 命令の依存を減らした(SMT無い時にけっこう改善してるはず)
- レジスタ少ない場合に対応。32bit版は性能マシになったと思われる(と思ったがそうでもない)
- NEON版はまじめに書いた(https://github.com/tanakamura/waifu2x-converter-cpp/blob/ef62a7964872d82823333e102dc3f09367065695/src/modelHandler_simd_unroll4.hpp#L86)
SSEは良くなると思ったが、Atomでは悪くなっている。これはSSEではbroadcastが弱くてロード+shuffleになってしまうから。Atomは2issueなので命令が増えた分そのまま遅くなってしまう。
まあAtomはOpenCLのほうが確実にはやいから忘れよう。
■
解説は…まあGPU版も動いたら考えよう。
簡単に説明するとブロッキングを頑張った。
前が
for (yi=0; yi<height; yi++) { for (xi=0; xi<width; xi++) { float sum[nOutputPlanes] = {0}; for (ip=0; ip<nInputPlane; ip++) { for (op=0; op<nOutputPlane; op++) { sum[op] += kernel(planes[ip][yi][xi], weight[ip][op]); } } for (op=0; op<nOutputPlane; op++) { o_planes[op][yi][xi] = ReLU(sum[op]); } } }
改善後が、
// L1 : 32KB // L2 : 256KB // reg : 256bit x 16 weight_reg[2]; // 2reg output_reg[5][2]; // 10reg input_reg[1] // 1reg // 13reg@FMA, 14reg@AVX,SSE // working set size // output input weight | total (where width=512) for (dposy=0; dposy<3; dposy++) { // 128*4*width 128*3*4*width 128*128*9*4 | 1.6MB for (ii0=0; ii0<nInputPlane; ii0+=32) { // 128*4*width 128* 4*width 128*128*3*4 | 704KB for (oi0=0; oi0<nOutput; oi0+=16) { // 128*4*width 32* 4*width 32*128*3*4 | 368KB for (x0=0; x0<width; x0+=5) { // 16*4*width 32* 4*width 32* 16*3*4 | 102KB (L2) output_reg[0..4][0..1] = 0; for (dposx=0; dposx<3; dposx++) { // 16*4*5(10reg) 32* 4*5 32* 16*3*4 | 7KB (L1) for (ii1=ii0; ii1<ii0+32; ii1++) {// 16*4*5 32* 4*5 32* 16* 4 | .. (L1) for (oi1=0; oi1<16; oi1++) { // 16*4*5 4*5 16* 4 | .. (L1) weight_reg[0] = weigh[oi0+0][ii1][dpos][oi1]; weight_reg[1] = weigh[oi0+1][ii1][dpos][oi1]; output_reg[0..4][0][oi1] += weight_reg[0] * input[ii1][x0+(0..4)+dpos]; output_reg[0..4][1][oi1] += weight_reg[1] * input[ii1][x0+(0..4)+dpos]; } } } if (ii0 == last) { output[xi0..xi0+4][oi0..oi0+1]=ReLU(output[][] + output_reg[0..4][0..1]); } else { output[xi0..xi0+4][oi0..oi0+1]+=output_reg[0..4][0..1]; } } } } }
こんな感じ。