Denver の最適化機能を調べる

2015年1月15日

NVIDIA 社の開発した Denver という 64bit ARM CPU は、ARM 機械語を最適化する機能を持っています。これをいくらか見てみようと思います

コード https://bitbucket.org/fixstars/blog/src/d2bfdedb9bdee4d994016f3583d1640425830c4c/small_test/denver-ipc.c?at=master

実行結果 : https://bitbucket.org/fixstars/blog/src/d2bfdedb9bdee4d994016f3583d1640425830c4c/small_test/denver-log.txt?at=master

Nexus9 の Android 上 で 64bit ELF を動かしています。コンパイルは、TADPを入れた環境で、

$ /usr/local/cuda-android/android-ndk-r10c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-gcc –sysroot /usr/local/cuda-android/android-ndk-r10c/platforms/android-21/arch-arm64 -fPIE -pie -O2 -g

などのようにしてください。

IPC は、 perf_event の PERF_COUNT_HW_CPU_CYCLES、PERF_COUNT_HW_INSTRUCTIONS から計算しています。

大量のnop

ループに大量の nop を入れます。

static void
nop_func(int iter)
{
    for (int i=0; i<100000; i++) {
        ITER32(__asm__ __volatile__ ("nop":::"memory"););
    }
}
                 nop- 0: 1.118293, clk=3046048, inst=3406375
                 nop- 1: 28.883114, clk=117781, inst=3401882
                 nop- 2: 28.916995, clk=117643, inst=3401882
                 nop- 3: 28.978823, clk=117392, inst=3401882
                 nop- 4: 28.978823, clk=117392, inst=3401882
                 nop- 5: 28.978823, clk=117392, inst=3401882
                 nop- 6: 28.978823, clk=117392, inst=3401882
                 nop- 7: 28.978823, clk=117392, inst=3401882
                 nop- 8: 28.978823, clk=117392, inst=3401882
                 nop- 9: 28.978823, clk=117392, inst=3401882
                 nop-10: 28.978823, clk=117392, inst=3401882

最初は IPC1.1ぐらいですが、二回目からは29 ぐらいになっています。

このことから、パフォーマンスカウンタの実行命令数はARM命令単位でカウントしていることと、IPCがスペック上の理論値 7 を超えていることから、nop は消えてしまうことがわかります。

無限にIPC増やしていけそうですが、どこかでリミットがかかっているようで、無限に増えるわけではないようです(組み合わせによっては52ぐらい出るのですがそれ以上確認できず)。

無駄な命令(整数)

いくらか演算したあとに、レジスタの値を初期化します

static void
int_nop_clear_func(int iter)
{
    int val=0;
    __asm__ __volatile__ ("mov %[val], 1\n\t"
                          :[val]"+r"(val));


    for (int i=0; i<100000; i++) {
        ITER32(__asm__ __volatile__ ("add %[val], %[val], %[val]\n\t"
                                     :[val]"+r"(val)

                   ););
        __asm__ __volatile__ ("mov %[val], 0\n\t"
                              :[val]"+r"(val));
    }
}
       int_nop_clear- 0: 0.511133, clk=6851221, inst=3501882
       int_nop_clear- 1: 0.440890, clk=7954954, inst=3507263
       int_nop_clear- 2: 0.669569, clk=5230052, inst=3501882
       int_nop_clear- 3: 16.883293, clk=207417, inst=3501882
       int_nop_clear- 4: 16.946531, clk=206643, inst=3501882
       int_nop_clear- 5: 17.039296, clk=205518, inst=3501882
       int_nop_clear- 6: 17.047508, clk=205419, inst=3501882
       int_nop_clear- 7: 17.050413, clk=205384, inst=3501882

これも命令が消えているように見えますね。無駄なaddは消えることがわかります。

ただ、ループARM命令の最適化はC言語の最適化よりも難しい処理で、割りこみルーチンがレジスタを読み書きするというのを考えると、割り込みが入る可能性のある箇所では、レジスタ単位でもとのプログラムの値を正しく再現する必要があります。おそらく、そういう理由があるので、プログラムが複雑になると、最適化はされないようです。

例えば、

static void
int_nop_func(int iter)
{
    int v1 = 1;
    int vsum = 0;
    for (int i=0; i<100000; i++) {
        ITER32(__asm__ __volatile__ ("mov %[v1], 1\n\t"
                                     "add %[vsum], %[vsum], %[v1]\n\t"
                                     :[v1]"+r"(v1), [vsum]"+r"(vsum));

            );
    }
    data[0][0] = vsum;
}

のように、レジスタに定数を入れる処理をすぐ隣に置くと、命令は削除されるようですが、

static void
int_nop_func(int iter)
{
    int v1 = 1;
    int vsum = 0;
    for (int i=0; i<100000; i++) {
        ITER32(__asm__ __volatile__ ("add %[vsum], %[vsum], %[v1]\n\t"
                                     :[v1]"+r"(v1), [vsum]"+r"(vsum));

            );
    }
    data[0][0] = vsum;
}

のように、レジスタの初期化をループ外に出したり、

static void
cond_nop_func(int iter)
{
    __asm__ __volatile__("mov w0, 1" ::: "w0");
    for (int i=0; i<100000; i++) {
        ITER32(__asm__ __volatile__ ("add w0, w0, w0\n\tcmp w0, 4\n\tbeq 1f\n\t1:\n\t":::"w0", "memory"););
        __asm__ __volatile__("mov w0, 1" ::: "w0");
    }
}

のように、途中に条件分岐を入れたりすると、命令は削除されないようです。

無駄な命令(浮動小数)

浮動小数演算はかなりシンプルな例でも削除されないようです。

 static void
fp_nop_func(int iter)
{
    for (int i=0; i<100000; i++) {
        ITER32(__asm__ __volatile__ ("fmov s0, 1.0e+0\n\t"
                                     "fadd s0, s0, s0\n\t"
                                     :
                                     :
                                     :"memory", "s0");
               
            );
    }
}

は、IPCが3.8ぐらいになります。

ARMv8 はよく知らないのでなんとも言えないですが、FPUは入力値やモード設定によって例外出す可能性があるので、命令を削除する最適化は入っていないのではないかという気がします。

最大IPC

NVIDIA社のblogにあるDenverの紹介を見ると

http://blogs.nvidia.com/blog/2014/08/11/tegra-k1-denver-64-bit-for-android/

JSR, ICU0, ICU1, FP0, FP1, LS0, LS1 が並んでいるように見えます。

名前からすると、ジャンプx1、整数x2、浮動小数x2、ロードストアx2 で 7way だと推測されます

いくらか試した範囲だと、↓ のようにすれば、IPC 5.4 になるようです。

static void
ipc7(int iter)
{

    int val=0, val2=0, val3=0, val4=0, val5=0, val6=0, val7 = 0;
    float fval=0, fval2=0, fval3=0;

    for (int i=0; i<100000; i++) {
        int *ptr = (int*)(data_large);
        int *ptr2 = (int*)(data_large+1024+16);

        ITER32(
            __asm__ __volatile__("ldr %w[reg], [%[ptr]], 8\n\t"
                                 "ldr %w[reg2], [%[ptr2]], 8\n\t"
                                 "add %[reg4], %[reg4], %[reg5]\n\t"
                                 "add %[reg5], %[reg5], %[reg5]\n\t"
                                 "add %[reg6], %[reg6], %[reg7]\n\t"
                                 //"add %[reg7], %[reg7], %[reg7]\n\t"
                                 "fadd %s[freg2], %s[freg], %s[freg]\n\t"
                                 "fadd %s[freg3], %s[freg], %s[freg]\n\t"
                                 :[reg]"+r"(val),
                                  [reg2]"+r"(val2),
                                  [reg3]"+r"(val3),
                                  [reg4]"+r"(val4),
                                  [reg5]"+r"(val5),
                                  [reg6]"+r"(val6),
                                  [reg7]"+r"(val7),
                                  [freg]"+w"(fval),
                                  [freg2]"+w"(fval2),
                                  [freg3]"+w"(fval3),
                                  [ptr]"+r"(ptr),
                                  [ptr2]"+r"(ptr2)
                );
            );
    }
}

5.4というのもよくわからない値ですね。あと整数命令は3つ入れたほうがIPCが上がるようです。 nop のところで書いたように消えたARM命令もカウントされてしまうので、何かそういうのが効いてるのかもしれません。

最適化がかかっていない最初の実行ではIPC 1程度になっています。

                ipc7- 0: 1.097943, clk=13617237, inst=14950954
                ipc7- 1: 5.285204, clk=2827535, inst=14944099
                ipc7- 2: 5.139465, clk=2908479, inst=14948025
                ipc7- 3: 5.272507, clk=2834344, inst=14944099
                ipc7- 4: 5.264390, clk=2838714, inst=14944099
                ipc7- 5: 5.277134, clk=2831859, inst=14944099
                ipc7- 6: 5.263929, clk=2838963, inst=14944099

一応依存の無いようにARM命令並べていますが、それではIPC上がらないようなので、ARMのハードウェアデコーダは、スループット1命令なのではないかと予想されます

ついでにいくつか試した値を書いておくと、

            __asm__ __volatile__("ldr %w[reg], [%[ptr]], 8\n\t"
                                 //"ldr %w[reg2], [%[ptr2]], 8\n\t"
                                 //"add %[reg4], %[reg4], %[reg5]\n\t"
                                 //"add %[reg5], %[reg5], %[reg5]\n\t"
                                 //"add %[reg6], %[reg6], %[reg7]\n\t"
                                 //"add %[reg7], %[reg7], %[reg7]\n\t"
                                 //"fadd %s[freg2], %s[freg], %s[freg]\n\t"
                                 //"fadd %s[freg3], %s[freg], %s[freg]\n\t"
                                 :[reg]"+r"(val),
                                  [reg2]"+r"(val2),
                                  [reg3]"+r"(val3),
                                  [reg4]"+r"(val4),
                                  [reg5]"+r"(val5),
                                  [reg6]"+r"(val6),
                                  [reg7]"+r"(val7),
                                  [freg]"+w"(fval),
                                  [freg2]"+w"(fval2),
                                  [freg3]"+w"(fval3),
                                  [ptr]"+r"(ptr),
                                  [ptr2]"+r"(ptr2)
                );

これが IPC8.8。32bit ARMは連続するアドレスを複数レジスタにロードする命令があるので、それに近いものに変換されてるように見えます。

            __asm__ __volatile__("ldr %w[reg], [%[ptr]], 8\n\t"
                                 "ldr %w[reg2], [%[ptr2]], 8\n\t"
                                 //"add %[reg4], %[reg4], %[reg5]\n\t"
                                 //"add %[reg5], %[reg5], %[reg5]\n\t"
                                 //"add %[reg6], %[reg6], %[reg7]\n\t"
                                 //"add %[reg7], %[reg7], %[reg7]\n\t"
                                 //"fadd %s[freg2], %s[freg], %s[freg]\n\t"
                                 //"fadd %s[freg3], %s[freg], %s[freg]\n\t"
                                 :[reg]"+r"(val),
                                  [reg2]"+r"(val2),
                                  [reg3]"+r"(val3),
                                  [reg4]"+r"(val4),
                                  [reg5]"+r"(val5),
                                  [reg6]"+r"(val6),
                                  [reg7]"+r"(val7),
                                  [freg]"+w"(fval),
                                  [freg2]"+w"(fval2),
                                  [freg3]"+w"(fval3),
                                  [ptr]"+r"(ptr),
                                  [ptr2]"+r"(ptr2)
                );

これが 2.4。 謎の値ですね。

            __asm__ __volatile__(//"ldr %w[reg], [%[ptr]], 8\n\t"
                                 //"ldr %w[reg2], [%[ptr2]], 8\n\t"
                                 "add %[reg4], %[reg4], %[reg5]\n\t"
                                 "add %[reg5], %[reg5], %[reg5]\n\t"
                                 "add %[reg6], %[reg6], %[reg7]\n\t"
                                 "add %[reg7], %[reg7], %[reg7]\n\t"
                                 //"fadd %s[freg2], %s[freg], %s[freg]\n\t"
                                 //"fadd %s[freg3], %s[freg], %s[freg]\n\t"
                                 :[reg]"+r"(val),
                                  [reg2]"+r"(val2),
                                  [reg3]"+r"(val3),
                                  [reg4]"+r"(val4),
                                  [reg5]"+r"(val5),
                                  [reg6]"+r"(val6),
                                  [reg7]"+r"(val7),
                                  [freg]"+w"(fval),
                                  [freg2]"+w"(fval2),
                                  [freg3]"+w"(fval3),
                                  [ptr]"+r"(ptr),
                                  [ptr2]"+r"(ptr2));

これが 3.8。 整数加算は4並列できる。

            __asm__ __volatile__(//"ldr %w[reg], [%[ptr]], 8\n\t"
                                 //"ldr %w[reg2], [%[ptr2]], 8\n\t"
                                 "and %[reg4], %[reg4], %[reg5]\n\t"
                                 "and %[reg5], %[reg5], %[reg5]\n\t"
                                 "and %[reg6], %[reg6], %[reg7]\n\t"
                                 "and %[reg7], %[reg7], %[reg7]\n\t"
                                 //"fadd %s[freg2], %s[freg], %s[freg]\n\t"
                                 //"fadd %s[freg3], %s[freg], %s[freg]\n\t"
                                 :[reg]"+r"(val),
                                  [reg2]"+r"(val2),
                                  [reg3]"+r"(val3),
                                  [reg4]"+r"(val4),
                                  [reg5]"+r"(val5),
                                  [reg6]"+r"(val6),
                                  [reg7]"+r"(val7),
                                  [freg]"+w"(fval),
                                  [freg2]"+w"(fval2),
                                  [freg3]"+w"(fval3),
                                  [ptr]"+r"(ptr),
                                  [ptr2]"+r"(ptr2)
                );

and は4.1。3.8との差は謎ですね。

整数演算のスケジューリング

Denver は、in-order でも高IPCという宣伝がされているので、なんらかの命令スケジューリングがされていると予想されます。実際、以下のようなコードを入れると、

static void
loop_int_dep(int iter)
{
    int val=0, val2=0;

    for (int j=0; j<128; j++) {
        int *ptr = (int*)(data_large+0);
        int *ptr2 = (int*)(data_large+128);

        if (iter > 8 && iter < 24) {
            ptr2 = (int*)(data_large+0);
        }

        for (int i=0; i<4096; i++) {
            ITER32(__asm__ __volatile__("ldr %w[reg], [%[ptr]]\n\t"
                                        "add %w[reg], %w[reg], 1\n\t"
                                        "str %w[reg], [%[ptr]]\n\t"

                                        "ldr %w[reg2], [%[ptr2]]\n\t"
                                        "add %w[reg2], %w[reg2], 1\n\t"
                                        "str %w[reg2], [%[ptr2]]\n\t"
                                        :[reg]"+r"(val),
                                         [reg2]"+r"(val2),
                                         [ptr]"+r"(ptr),
                                         [ptr2]"+r"(ptr2)););
        }
    }
}

何回か実行すると、IPC 3 ぐらいになるので、スケジューリングが行われていることが確認できます。

上の関数では、計測用に32回関数を実行していて、9〜23回目の処理は、スケジューリングができないようにしています。

        loop_int_dep- 0: 2.535232, clk=40134092, inst=101749245
        loop_int_dep- 1: 2.922908, clk=34802246, inst=101723746
        loop_int_dep- 2: 2.928609, clk=34732553, inst=101718050
        loop_int_dep- 3: 2.924591, clk=34782669, inst=101725097
        loop_int_dep- 4: 2.928548, clk=34733520, inst=101718787
        loop_int_dep- 5: 2.924948, clk=34777964, inst=101723746
        loop_int_dep- 6: 2.928742, clk=34730970, inst=101718050
        loop_int_dep- 7: 2.925112, clk=34776014, inst=101723746
        loop_int_dep- 8: 2.928069, clk=34739210, inst=101718787
        loop_int_dep- 9: 0.743494, clk=136840199, inst=101739872
        loop_int_dep-10: 0.743998, clk=136745227, inst=101738176
        loop_int_dep-11: 0.743323, clk=136873555, inst=101741215
        loop_int_dep-12: 0.744145, clk=136720483, inst=101739864
        loop_int_dep-13: 0.743707, clk=136801106, inst=101739886
        loop_int_dep-14: 0.741004, clk=137332227, inst=101763765
        loop_int_dep-15: 1.031812, clk=98597544, inst=101734168
        loop_int_dep-16: 2.924615, clk=34780771, inst=101720379
        loop_int_dep-17: 2.926805, clk=34755262, inst=101721861
        loop_int_dep-18: 2.926108, clk=34762780, inst=101719632
        loop_int_dep-19: 2.927046, clk=34752391, inst=101721839
        loop_int_dep-20: 2.926114, clk=34762945, inst=101720347
        loop_int_dep-21: 2.927109, clk=34751674, inst=101721930
        loop_int_dep-22: 2.928033, clk=34739341, inst=101717922
        loop_int_dep-23: 2.924022, clk=34789375, inst=101724908
        loop_int_dep-24: 2.919629, clk=34841676, inst=101724772
        loop_int_dep-25: 2.923048, clk=34800538, inst=101723655
        loop_int_dep-26: 2.928477, clk=34734111, inst=101718050
        loop_int_dep-27: 2.919913, clk=34839363, inst=101727892
        loop_int_dep-28: 2.927462, clk=34746382, inst=101718696
        loop_int_dep-29: 2.924561, clk=34782568, inst=101723746
        loop_int_dep-30: 2.928483, clk=34734039, inst=101718050
        loop_int_dep-31: 2.926565, clk=34758150, inst=101721989

結果は、上のとおりです。

メモリアクセスを投機実行しているはずで、9〜23回目では、これは失敗します。失敗したあと、しばらく0.75ぐらいが続いて、またしばらくすると、3程度に戻ります。

二回目のテストでは、

        loop_int_dep- 0: 2.912313, clk=34933380, inst=101736943
        loop_int_dep- 1: 2.927798, clk=34742419, inst=101718796
        loop_int_dep- 2: 2.923490, clk=34795326, inst=101723786
        loop_int_dep- 3: 2.928089, clk=34738689, inst=101717968
        loop_int_dep- 4: 2.924932, clk=34778167, inst=101723764
        loop_int_dep- 5: 2.925973, clk=34764568, inst=101720184
        loop_int_dep- 6: 2.925029, clk=34777027, inst=101723817
        loop_int_dep- 7: 2.928099, clk=34738573, inst=101717968
        loop_int_dep- 8: 2.926438, clk=34759923, inst=101722748
        loop_int_dep- 9: 2.926079, clk=34763370, inst=101720356
        loop_int_dep-10: 2.927156, clk=34751120, inst=101721948
        loop_int_dep-11: 2.923855, clk=34790476, inst=101722316
        loop_int_dep-12: 2.926082, clk=34763844, inst=101721857
        loop_int_dep-13: 2.926529, clk=34758025, inst=101720356
        loop_int_dep-14: 2.925821, clk=34767442, inst=101723299
        loop_int_dep-15: 2.928578, clk=34732874, inst=101717931
        loop_int_dep-16: 2.924696, clk=34780929, inst=101723658
        loop_int_dep-17: 2.927208, clk=34750477, inst=101721857
        loop_int_dep-18: 2.924523, clk=34782311, inst=101721665
        loop_int_dep-19: 2.911301, clk=34947141, inst=101741638
        loop_int_dep-20: 2.925671, clk=34767933, inst=101719528
        loop_int_dep-21: 2.926479, clk=34759153, inst=101721948
        loop_int_dep-22: 2.926705, clk=34755930, inst=101720356
        loop_int_dep-23: 2.926610, clk=34757608, inst=101721948
        loop_int_dep-24: 2.928596, clk=34732705, inst=101718059
        loop_int_dep-25: 2.924693, clk=34780980, inst=101723695
        loop_int_dep-26: 2.928238, clk=34737208, inst=101718796
        loop_int_dep-27: 2.923950, clk=34790300, inst=101725115
        loop_int_dep-28: 2.928110, clk=34738469, inst=101718059
        loop_int_dep-29: 2.921115, clk=34824739, inst=101727061
        loop_int_dep-30: 2.926998, clk=34751931, inst=101718818
        loop_int_dep-31: 2.927037, clk=34752549, inst=101721985

このように、投機実行に失敗しているのを感じさせない値になります。よほど特殊な命令を実装しているのでない限り、これをひとつのループで実現することはできないと思うので、

  • ポインタ重なったときのループ
  • ポインタ違うときのループ

の2バージョンのループを作っているのではないかと思います。

浮動小数演算のスケジューリング

static void
loop_fp_dep(int iter)
{
    
    float reg=0;

    for (int j=0; j<128; j++) {
        float *ptr = (float*)(fp_data_large);
        float *ptr2 = (float*)(fp_data_large+128);
        if (iter > 8 && iter < 24) {
            ptr2 = (float*)fp_data_large;
        }

        for (int i=0; i<1024; i++) {
            ITER32(__asm__ __volatile__("ldr %s[reg], [%[ptr]]\n\t"
                                        "fadd %s[reg], %s[reg], %s[reg]\n\t"
                                        "str %s[reg], [%[ptr]]\n\t"

                                        "ldr %s[reg], [%[ptr2]]\n\t"
                                        "fadd %s[reg], %s[reg], %s[reg]\n\t"
                                        "str %s[reg], [%[ptr2]]\n\t"

                                        :[reg]"+w"(reg),
                                         [ptr]"+r"(ptr),
                                        [ptr2]"+r"(ptr2));
                );
        }
    }
}

浮動小数で同じような処理を実行した場合の結果が、

         loop_fp_dep- 0: 0.769715, clk=33085834, inst=25466651
         loop_fp_dep- 1: 1.391458, clk=18281149, inst=25437442
         loop_fp_dep- 2: 1.395606, clk=18224979, inst=25434882
         loop_fp_dep- 3: 1.396779, clk=18208007, inst=25432559
         loop_fp_dep- 4: 1.396975, clk=18206595, inst=25434167
         loop_fp_dep- 5: 1.397371, clk=18201888, inst=25434791
         loop_fp_dep- 6: 1.397803, clk=18195796, inst=25434145
         loop_fp_dep- 7: 1.394573, clk=18238471, inst=25434881
         loop_fp_dep- 8: 1.396573, clk=18212372, inst=25434904
         loop_fp_dep- 9: 0.138509, clk=183860933, inst=25466446
         loop_fp_dep-10: 0.138527, clk=183842089, inst=25467067
         loop_fp_dep-11: 0.138512, clk=183870291, inst=25468200
         loop_fp_dep-12: 0.138557, clk=183773406, inst=25463143
         loop_fp_dep-13: 0.138428, clk=184003031, inst=25471206
         loop_fp_dep-14: 0.138396, clk=184148742, inst=25485498
         loop_fp_dep-15: 0.138064, clk=184493143, inst=25471895
         loop_fp_dep-16: 0.137496, clk=185191576, inst=25463094
         loop_fp_dep-17: 0.137469, clk=185245081, inst=25465463
         loop_fp_dep-18: 0.137478, clk=185262439, inst=25469551
         loop_fp_dep-19: 0.137497, clk=185190397, inst=25463163
         loop_fp_dep-20: 0.137492, clk=185223010, inst=25466767
         loop_fp_dep-21: 0.137511, clk=185183573, inst=25464787
         loop_fp_dep-22: 0.137512, clk=185169910, inst=25463094
         loop_fp_dep-23: 0.137504, clk=185215863, inst=25467866
         loop_fp_dep-24: 0.431694, clk=58939348, inst=25443777
         loop_fp_dep-25: 0.431779, clk=58918464, inst=25439769
         loop_fp_dep-26: 0.431835, clk=58914695, inst=25441424
         loop_fp_dep-27: 0.431673, clk=58942253, inst=25443777
         loop_fp_dep-28: 0.429710, clk=59250775, inst=25460679
         loop_fp_dep-29: 0.431422, clk=58982682, inst=25446399
         loop_fp_dep-30: 0.431863, clk=58903057, inst=25438062
         loop_fp_dep-31: 0.431356, clk=58993471, inst=25447198

このようになります。

整数では、投機実行失敗後、しばらくするとIPC3 に戻りましたが、浮動小数では、1回遅くなると、24回目以降も遅くなったままなので、投機実行をやめているように見えます。

unroll

static void
unroll_func(int iter)
{
    int v0=0, v1=1, v2=3;
    int *p = data_large;
    for (int i=0; i<100000; i++) {
        __asm__ __volatile__("ldr %w[v0], [%[ptr]]\n\t"
                             "add %w[v0], %w[v0], %w[v1]\n\t"
                             "add %w[v0], %w[v0], %w[v2]\n\t"
                             "str %w[v0], [%[ptr]], 4\n\t"
                             :[v0]"+r"(v0),
                              [v1]"+r"(v1),
                              [v2]"+r"(v2),
                              [ptr]"+r"(p));
    }
}

ループ中では依存があり、ループのイテレーション間では依存が無いという処理です。

これはIPC3.1程度になるので、アンロールかそれに相当する最適化をしていると予想されます。

間接分岐

関数ポインタを付けかえて間接分岐します。

void ret(){
    noarg_func_ptr = ret2;
    return ;
} 
void ret2(){
    noarg_func_ptr = ret3;
    return ;
} 
void ret3(){
    noarg_func_ptr = ret4;
    return ;
} 
void ret4(){
    noarg_func_ptr = ret5;
    return ;
} 
void ret5(){
    noarg_func_ptr = ret6;
    return ;
} 
void ret6(){
    noarg_func_ptr = ret7;
    return ;
} 
void ret7(){
    noarg_func_ptr = ret8;
    return ;
} 
void ret8(){
    noarg_func_ptr = ret9;
    return ;
} 
void ret9(){
    noarg_func_ptr = ret;
    return ;
} 

static void
indirect_call(int iter)
{
    int val = 0;

    for (int i=0; i<100000; i++) {
        ITER32(noarg_func_ptr(););
    }
}

使う関数の数(noarg_func_ptr = ret するまでの数)を変えると、

関数の数 1 2 3 4 5 6 7 8 9
IPC 2.0 1.2 0.7 1.1 0.6 0.7 0.6 2.0 0.5

のようになります。最適化の結果なのか、BTBのway構成の結果なのかはちょっとよくわからないですね…

感想

あまり派手なことはしていないですが、地道な作業はひととおりやっているという印象を受けます。

Tags

About Author

nakamura

Leave a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

Recent Comments

Social Media