うーん…
#include <stdio.h> #include <stdlib.h> #include <emmintrin.h> static unsigned int rdtsc(void) { unsigned int lo,hi; asm volatile ("rdtsc" :"=a"(lo), "=d"(hi)); return lo; } #define W 256 #define H 4096 void copy1(__m128i *src, __m128i *dest) { int i,j; for (i=0; i<H; i++) { for (j=0; j<W; j++) { dest[(i+0)*W+j] = src[(i+0)*W+j]; } } } void copy2(__m128i *src, __m128i *dest) { int i,j; for (i=0; i<H; i+=2) { for (j=0; j<W; j++) { __m128i a,b; a = src[(i+0)*W+j]; b = src[(i+1)*W+j]; dest[(i+0)*W+j] = a; dest[(i+1)*W+j] = b; } } } void copy4(__m128i *src, __m128i *dest) { int i,j; __m128i a,b,c,d; for (i=0; i<H; i+=4) { for (j=0; j<W; j++) { a = src[(i+0)*W+j]; b = src[(i+1)*W+j]; c = src[(i+2)*W+j]; d = src[(i+3)*W+j]; dest[(i+0)*W+j] = a; dest[(i+1)*W+j] = b; dest[(i+2)*W+j] = c; dest[(i+3)*W+j] = d; } } } void copy16(__m128i *src, __m128i *dest) { int i,j; for (i=0; i<H; i+=16) { for (j=0; j<W; j++) { dest[(i+0)*W+j] = src[(i+0)*W+j]; dest[(i+1)*W+j] = src[(i+1)*W+j]; dest[(i+2)*W+j] = src[(i+2)*W+j]; dest[(i+3)*W+j] = src[(i+3)*W+j]; dest[(i+4)*W+j] = src[(i+4)*W+j]; dest[(i+5)*W+j] = src[(i+5)*W+j]; dest[(i+6)*W+j] = src[(i+6)*W+j]; dest[(i+7)*W+j] = src[(i+7)*W+j]; dest[(i+8)*W+j] = src[(i+8)*W+j]; dest[(i+9)*W+j] = src[(i+9)*W+j]; dest[(i+10)*W+j] = src[(i+10)*W+j]; dest[(i+11)*W+j] = src[(i+11)*W+j]; dest[(i+12)*W+j] = src[(i+12)*W+j]; dest[(i+13)*W+j] = src[(i+13)*W+j]; dest[(i+14)*W+j] = src[(i+14)*W+j]; dest[(i+15)*W+j] = src[(i+15)*W+j]; } } } void copyN(__m128i *src, __m128i *dest, int n) { int i,j,k; for (i=0; i<H; i+=n) { for (j=0; j<W; j++) { for (k=0; k<n; k++) { dest[(i+k)*W+j] = src[(i+k)*W+j]; } } } } int main() { unsigned int b[7],e[7]; void *p; __m128i *src; __m128i *dest; posix_memalign(&p, 16, 4096*8192); src = p; posix_memalign(&p, 16, 4096*8192); dest = p; copy1(dest, src); copy2(dest, src); copy4(dest, src); copy16(dest, src); copyN(dest, src,64); b[0] = rdtsc(); copy1(dest, src); copy1(dest, src); copy1(dest, src); copy1(dest, src); e[0] = rdtsc(); b[1] = rdtsc(); copy2(dest, src); copy2(dest, src); copy2(dest, src); copy2(dest, src); e[1] = rdtsc(); b[2] = rdtsc(); copy4(dest, src); copy4(dest, src); copy4(dest, src); copy4(dest, src); e[2] = rdtsc(); b[3] = rdtsc(); copy16(dest, src); copy16(dest, src); copy16(dest, src); copy16(dest, src); e[3] = rdtsc(); b[4] = rdtsc(); copyN(dest, src, 64); copyN(dest, src, 64); copyN(dest, src, 64); copyN(dest, src, 64); e[4] = rdtsc(); b[5] = rdtsc(); copyN(dest, src, 128); copyN(dest, src, 128); copyN(dest, src, 128); copyN(dest, src, 128); e[5] = rdtsc(); b[6] = rdtsc(); copyN(dest, src, 512); copyN(dest, src, 512); copyN(dest, src, 512); copyN(dest, src, 512); e[6] = rdtsc(); printf(" 1:%d\n",e[0]-b[0]); printf(" 2:%d\n",e[1]-b[1]); printf(" 4:%d\n",e[2]-b[2]); printf(" 16:%d\n",e[3]-b[3]); printf(" 64:%d\n",e[4]-b[4]); printf(" 128:%d\n",e[5]-b[5]); printf("1024:%d\n",e[6]-b[6]); return 0; }
これの結果が、
1:95653020 2:96958104 4:99469308 16:126624288 64:232365708 128:297237984 1024:441126636
こんな感じであるが、さて、これ、それぞれの違いはどこから来るでしょう?という問題で、多分、
1:95653020 2:96958104 /* ?? */ 4:99469308 /* ?? */ 16:126624288 /* プリフェッチが効かなくなる */ 64:232365708 /* L1Dの連想度的によくない */ 128:297237984 /* ?? */ 1024:441126636 /* L2の連想度的によくない */
なのかなー、という感じであるが、いまいち確証が持てない。
あと布団から出るのめんどくさくて、メインマシンがノートPCになりつつある昨今であり、Core2でも試すべきだが試していない。
x86のキャッシュは、
- キャッシュライン64byte
- L1D 8way 6〜8ライン
- L2 8way 数千ライン
- プリフェッチ8本
などとパラメータがたくさんあるのに加えて、ソフトウェアでプリフェッチ入れるとその命令自体が重くて遅くなったりするなど、何が最適かよーわからんというのがあって、さて、この問題を解くのは人間がやるべきか、自動でやるべきか?特に結論は無い。