Atomのレイテンシとスループットをひたすら計測する
SSEとか
AtomのRDTSCはスリープしてると止まってる(?)ようなので注意
これの12章がちゃんと解説してあるのでこれ読むべき
http://www.intel.com/Assets/PDF/manual/248966.pdf
(結果の時間が/2してあるのは、1600MHzのCPUを800MHzで動かしているからです。必要なら i*16.0*2 → i*16.0に修正して)
#include <stdio.h> #include <emmintrin.h> unsigned int rdtsc() { unsigned int eax, edx; asm volatile ("rdtsc" :"=a"(eax), "=d"(edx)); return eax; } #define N 7 #define SCALE 8 #define NTHREAD 2 #define A16(P) \ P P P P \ P P P P \ P P P P \ P P P P \ #define GEN(name, A) \ int time_##name[NTHREAD][N]; \ int \ name(int a, int b, int n, int x, int t) \ { \ int ret; \ unsigned int begin, end; \ int i; \ __m128i m1[16], m2[16]; \ \ begin = rdtsc(); \ for (i=n-1; i>=0; i--) { \ asm volatile (A16(A) \ : \ :"S"(m1), "D"(m2),"a"(0) \ :"ebx", "ecx", "edx" \ , \ "xmm0", \ "xmm1", \ "xmm2", \ "xmm3", \ "xmm4", \ "xmm5", \ "xmm6", \ "xmm7" \ ); \ \ } \ asm("xor %%eax, %%eax":::"eax"); \ asm("xor %%ebx, %%ebx":::"ebx"); \ asm("xor %%ecx, %%ecx":::"ecx"); \ asm("xor %%edx, %%edx":::"edx"); \ end = rdtsc(); \ \ asm volatile ("emms"); \ time_##name[t][x] = end-begin; \ \ return ret; \ } GEN(add, "paddd %%xmm0, %%xmm1\n\t") GEN(paraadd, "paddd %%xmm0, %%xmm1\n\tpaddd %%xmm2, %%xmm3\n\t"); GEN(loadadd, "paddd 16(%%esi,%%eax,8), %%xmm1\n\t"); GEN(add_loadadd, "paddd 16(%%esi,%%eax,8), %%xmm1\n\tpaddd %%xmm2, %%xmm3\n\t"); GEN(lea_loadadd, "addl 16(%%esi,%%eax,8), %%edx\n\tleal (%%esi,%%eax,8), %%ecx\n\t"); GEN(paraloadadd, "paddd 16(%%esi,%%eax,8), %%xmm1\n\tpaddd (%%edi,%%eax,8), %%xmm3\n\t"); GEN(mulps, "mulps %%xmm0, %%xmm1\n\t"); GEN(depmulps, "mulps %%xmm0, %%xmm0\n\t" ); GEN(depadd, "paddd %%xmm0, %%xmm0\n\t" ); GEN(paramulps, "mulps %%xmm0, %%xmm1\n\t" "mulps %%xmm0, %%xmm2\n\t" "mulps %%xmm0, %%xmm3\n\t" "mulps %%xmm0, %%xmm4\n\t" "mulps %%xmm0, %%xmm5\n\t" "mulps %%xmm0, %%xmm6\n\t" "mulps %%xmm0, %%xmm7\n\t" ); GEN(paramulpsinc, "mulps %%xmm0, %%xmm1\n\t" "incl (%%esi)\n\t" "mulps %%xmm0, %%xmm2\n\t" "incl (%%esi)\n\t" "mulps %%xmm0, %%xmm3\n\t" "incl (%%esi)\n\t" "mulps %%xmm0, %%xmm4\n\t" "incl (%%esi)\n\t" "mulps %%xmm0, %%xmm5\n\t" "incl (%%esi)\n\t" "mulps %%xmm0, %%xmm6\n\t" "incl (%%esi)\n\t" ); GEN(paracmpps, "cmpeqps %%xmm0, %%xmm1\n\t" "cmpeqps %%xmm0, %%xmm2\n\t" "cmpeqps %%xmm0, %%xmm3\n\t" "cmpeqps %%xmm0, %%xmm4\n\t" "cmpeqps %%xmm0, %%xmm5\n\t" "cmpeqps %%xmm0, %%xmm6\n\t" "cmpeqps %%xmm0, %%xmm7\n\t" ); GEN(paraaddcmpps, "cmpeqps %%xmm0, %%xmm1\n\t" "add %%eax, %%eax\n\t" "cmpeqps %%xmm0, %%xmm2\n\t" "add %%eax, %%eax\n\t" "cmpeqps %%xmm0, %%xmm3\n\t" "add %%eax, %%eax\n\t" "cmpeqps %%xmm0, %%xmm4\n\t" "add %%eax, %%eax\n\t" "cmpeqps %%xmm0, %%xmm5\n\t" "add %%eax, %%eax\n\t" "cmpeqps %%xmm0, %%xmm6\n\t" "add %%eax, %%eax\n\t" "cmpeqps %%xmm0, %%xmm7\n\t" "add %%eax, %%eax\n\t" ); int time_load[N]; int load(int a, int b, int n, int x) { int ret; unsigned int begin, end; int i; int stack, stack2; begin = rdtsc(); for (i=0; i<n; i++) { asm volatile (A16("mov (%%eax), %%ecx\n\t" "add %%ecx, %%edx\n\t") : :"a"(&stack2), "S"(&stack2) :"ecx", "edx"); } end = rdtsc(); time_load[x] = end-begin; return ret; } asm("hoge:ret"); extern int hoge; int *hoge_adr = &hoge; int alias_flag; int time_para_loadstore[N]; int para_loadstore(int a, int b, int n, int x) { int ret; unsigned int begin, end; int i; int *p; int stack, stack2; if (alias_flag) { p = &stack2; } else { p = &stack; } begin = rdtsc(); for (i=0; i<n; i++) { asm volatile (A16("add %%ecx, (%%eax)\n\t" "add %%edx, (%%esi)\n\t") : :"a"(p), "S"(&stack2) :"ecx", "edx"); } end = rdtsc(); time_para_loadstore[x] = end-begin; return ret; } int jmp_cond; int time_condjmp[N]; int condjmp(int a, int b, int n, int x) { int ret; unsigned int begin, end; int i; int *p; int stack, stack2; begin = rdtsc(); for (i=0; i<n; i++) { asm volatile (A16("movl jmp_cond, %%eax\n\t" "cmpl $0, %%eax\n\t" "je 1f\n\t" "1:\n\t") :::"eax"); jmp_cond = !jmp_cond; } end = rdtsc(); time_condjmp[x] = end-begin; return ret; } void run(const char *name, int (*a)[N], int (*f)(int,int,int,int,int)) { int i, j; puts(name); for (j=0, i=1; j<N; j++, i*=SCALE) { f(100, 4, i, j, 0); } for (j=0, i=1; j<N; j++, i*=SCALE) { printf("%d:%f\n", i, a[0][j]/(i*16.0*2)); } } struct thread_arg { int (*f)(int,int,int,int,int); int t; }; static void * run_thread(void *d) { struct thread_arg *a = (struct thread_arg*)d; int (*f)(int,int,int,int,int) = a->f; int t = a->t; int j, i; for (j=0, i=1; j<N; j++, i*=SCALE) { f(100, 4, i, j, t); } } void run2(const char *name, int (*a)[N], int (*f)(int,int,int,int,int)) { pthread_t threads[NTHREAD]; struct thread_arg args[NTHREAD]; puts(name); int i,j,t; for (i=0; i<NTHREAD; i++) { args[i].f = f; args[i].t = i; pthread_create(&threads[i], NULL, run_thread, &args[i]); } for (i=0; i<NTHREAD; i++) { pthread_join(threads[i], NULL); } for (j=0, i=1; j<N; j++, i*=SCALE) { for (t=0; t<NTHREAD; t++) { printf("%d:%d:%f\n", t, i, a[t][j]/(i*16.0*2)); } } } int main() { unsigned int t, u; t = rdtsc(); u = rdtsc(); printf("%d\n", u-t); run2("add", time_add, add); run2("paraadd", time_paraadd, paraadd); run2("loadadd", time_loadadd, loadadd); run("loadadd", time_loadadd, loadadd); run2("lea_loadadd", time_lea_loadadd, lea_loadadd); run2("paraloadadd", time_paraloadadd, paraloadadd); run2("mulps", time_mulps, mulps); run2("paramulps", time_paramulps, paramulps); run2("depmulps", time_depmulps, depmulps); run("depmulps", time_depmulps, depmulps); run2("depadd", time_depadd, depadd); run("depadd", time_depadd, depadd); run2("paracmpps", time_paracmpps, paracmpps); run2("paraaddcmpps", time_paraaddcmpps, paraaddcmpps); run2("paramulpsinc", time_paramulpsinc, paramulpsinc); }
以下が結果。(多分)サイクル数です。
60 add 0:1:250.875000 1:1:155.625000 0:8:2.250000 1:8:2.671875 0:64:1.587891 1:64:2.103516 0:512:1.841309 1:512:2.041992 0:4096:2.004456 1:4096:2.004364 0:32768:2.000542 1:32768:2.000095 0:262144:2.003976 1:262144:2.019859 paraadd 0:1:97.500000 1:1:148.125000 0:8:3.093750 1:8:3.093750 0:64:2.326172 1:64:2.367188 0:512:2.260986 1:512:2.271240 0:4096:2.251099 1:4096:2.251007 0:32768:2.250126 1:32768:2.250126 0:262144:2.284731 1:262144:2.258022 loadadd 0:1:94.875000 1:1:125.625000 0:8:2.906250 1:8:2.390625 0:64:2.384766 1:64:2.273438 0:512:2.377441 1:512:2.373779 0:4096:2.374786 1:4096:2.374695 0:32768:2.406303 1:32768:2.376858 0:262144:2.283870 1:262144:2.423914 loadadd 1:12.000000 8:1.828125 64:1.324219 512:1.198242 4096:1.188812 32768:1.187656 262144:1.193570 lea_loadadd 0:1:7.500000 1:1:18.375000 0:8:3.234375 1:8:3.000000 0:64:2.513672 1:64:2.425781 0:512:2.383301 1:512:2.382568 0:4096:2.375885 1:4096:2.376068 0:32768:2.375118 1:32768:2.375233 0:262144:2.380949 1:262144:2.396100 paraloadadd 0:1:99.375000 1:1:142.500000 0:8:5.109375 1:8:4.312500 0:64:4.406250 1:64:3.503906 0:512:4.376953 1:512:4.371094 0:4096:4.374756 1:4096:4.374756 0:32768:4.348846 1:32768:4.423119 0:262144:4.379391 1:262144:4.378098 mulps 0:1:94.500000 1:1:111.375000 0:8:5.859375 1:8:6.234375 0:64:5.150391 1:64:5.337891 0:512:5.022217 1:512:5.014160 0:4096:5.002167 1:4096:5.002167 0:32768:5.000256 1:32768:5.000278 0:262144:5.023706 1:262144:5.017851 paramulps 0:1:121.875000 1:1:140.625000 0:8:29.015625 1:8:15.937500 0:64:28.031250 1:64:26.408203 0:512:28.048096 1:512:28.061279 0:4096:28.064026 1:4096:28.059814 0:32768:28.033710 1:32768:28.108852 0:262144:28.053105 1:262144:28.074702 depmulps 0:1:95.625000 1:1:117.375000 0:8:5.437500 1:8:5.859375 0:64:5.156250 1:64:5.361328 0:512:5.017822 1:512:5.013428 0:4096:5.002075 1:4096:5.002167 0:32768:5.000267 1:32768:5.000267 0:262144:5.022479 1:262144:5.015289 depmulps 1:9.000000 8:5.484375 64:5.103516 512:5.007568 4096:5.000793 32768:5.000095 262144:5.017482 depadd 0:1:98.250000 1:1:124.500000 0:8:3.046875 1:8:2.156250 0:64:2.138672 1:64:1.710938 0:512:2.021484 1:512:1.984131 0:4096:2.002167 1:4096:2.002075 0:32768:2.000278 1:32768:2.000267 0:262144:2.028760 1:262144:2.014790 depadd 1:6.375000 8:1.734375 64:1.218750 512:1.130859 4096:1.125824 32768:1.125103 262144:1.134241 paracmpps 0:1:141.000000 1:1:168.375000 0:8:56.906250 1:8:55.265625 0:64:56.384766 1:64:56.408203 0:512:56.256592 1:512:56.257324 0:4096:56.250916 1:4096:56.250916 0:32768:56.480621 1:32768:56.411808 0:262144:56.359689 1:262144:56.371603 paraaddcmpps 0:1:176.250000 1:1:192.375000 0:8:84.656250 1:8:81.421875 0:64:84.222656 1:64:84.275391 0:512:86.117432 1:512:84.734619 0:4096:85.044434 1:4096:84.028381 0:32768:84.334614 1:32768:84.279980 0:262144:84.332231 1:262144:84.277921 paramulpsinc 0:1:121.875000 1:1:163.500000 0:8:25.359375 1:8:17.671875 0:64:23.542969 1:64:25.007812 0:512:24.819580 1:512:23.955322 0:4096:24.089539 1:4096:24.832947 0:32768:24.470547 1:32768:24.319256 0:262144:24.294250 1:262144:24.414033
マニュアルのTable12-1を見るとわかるように、SSE128bitの整数演算は2命令/cycleできて、 これは、Nehalemとかと一緒である(とは言っても、CoreMAとNehalemはそれに加えて浮動小数演算ができるのだけど)。 paraaddの実測を見ても、これが事実であることは確認できる。
x86の特徴のひとつに強力なアドレス生成がある。 レジスタひとつをシフトしたあと、もう片方のレジスタとの加算を行い、それに定数を足すというのを 色んな命令のオマケに実行できる。 あとアドレスを生成するだけのlea命令もある。
loadadd(ロードと加算が一回)、add_loadadd(加算とロード加算一回づつ)、 paraloadadd(ロード加算を二回)、lea_loadadd(leaとロード加算一回づつ)の結果を見るに、
名前 | 命令列 | スループット |
loadadd | paddd 16(%%esi,%%eax,8), %%xmm1 | 1.1 |
add_loadadd | paddd 16(%%esi,%%eax,8), %%xmm1; paddd %%xmm2, %%xmm3 | 1.2 |
paraloadadd | paddd 16(%%esi,%%eax,8), %%xmm1;paddd (%%edi,%%eax,8), %%xmm3 | 0.5 |
lea_loadadd | addl 16(%%esi,%%eax,8), %%edx; leal (%%esi,%%eax,8), %%ecx | 1.2 |
どっちか片方のパイプではアドレス生成+ロード+演算ができて、両方のパイプでアドレス生成+演算ができるのが確認できる。 あとlea_loadaddはpadddだと1じゃなかったのでスカラ演算で。SSEで1にならないのは命令が長いからか?あとで調べる。
(んー?Table12-1はmulpsがスループット0.5になってるが、Table12-2はスループット1になってるように見えるが。 実測が0.5なのでTable12-1が正しいかなーという気がする。)
(面倒になってきたのであとで書きたい)
あとcmppsがスループット1/6で遅すぎる。cmpssのスループットが1なのでスカラで比較したほうが速い…。 Atomでは比較は注意して使ったほうがいい。あとparaaddcmppsの結果を見る限り、並行して何か実行できるわけでも無いらしい。
depmulps の結果を見ると、
種類 | スループット |
depmul(1スレッド) | 1/5 |
depmul(2スレッド) | 1/5 |
というように、スレッドふたつ動かしても、スレッド一個の性能は落ちていない(これ説明になっていないな…)。 なので、命令のレイテンシを隠蔽するのにHTTを使えるのがわかる。
んで、depaddの結果を見ると、
種類 | スループット |
depadd(1スレッド) | 1 |
depadd(2スレッド) | 1/2 |
というように、スレッド一個の性能が落ちてる。
上で見たように、padddは二並列できるはずだが、スレッドふたつ動かしたからと言って、これがペアになるわけではないようだ。 つまり、Atomではスーパースカラの効率を上げるためにHTTを使えるわけではない。
なので、HTTを使う場合は、
なのであった。(全然説明になっていない)
もうちょっとちゃんとした解説を書くべき
2009/09/06 Ver 1.0 書いた