周回遅れではないAtom話

概要

Atomのレイテンシとスループットをひたすら計測する

前提知識

SSEとか

RDTSCについて

AtomのRDTSCはスリープしてると止まってる(?)ようなので注意

Optimization Reference Manual について

これの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の実測を見ても、これが事実であることは確認できる。

AGU

x86の特徴のひとつに強力なアドレス生成がある。 レジスタひとつをシフトしたあと、もう片方のレジスタとの加算を行い、それに定数を足すというのを 色んな命令のオマケに実行できる。 あとアドレスを生成するだけのlea命令もある。

loadadd(ロードと加算が一回)、add_loadadd(加算とロード加算一回づつ)、 paraloadadd(ロード加算を二回)、lea_loadadd(leaとロード加算一回づつ)の結果を見るに、

名前命令列スループット
loadaddpaddd 16(%%esi,%%eax,8), %%xmm11.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の結果を見る限り、並行して何か実行できるわけでも無いらしい。

Hyper-Threading Technology (HTT)

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 書いた