速度ではなくサイズを最適化すると、GCC が 15 ~ 20% 高速なコードを生成するのはなぜですか? 質問する

速度ではなくサイズを最適化すると、GCC が 15 ~ 20% 高速なコードを生成するのはなぜですか? 質問する

2009 年に初めて、GCC (少なくとも私のプロジェクトとマシンでは) では、速度 (または)ではなくサイズ( ) を最適化すると、著しく高速なコードが生成される傾向があることに気付きました。それ以来、その理由を疑問に思っていました。-Os-O2-O3

私は、この驚くべき動作を示し、ここに投稿できるほど小さい (かなりばかげた) コードを作成することができました。

const int LOOP_BOUND = 200000000;

__attribute__((noinline))
static int add(const int& x, const int& y) {
    return x + y;
}

__attribute__((noinline))
static int work(int xval, int yval) {
    int sum(0);
    for (int i=0; i<LOOP_BOUND; ++i) {
        int x(xval+sum);
        int y(yval+sum);
        int z = add(x, y);
        sum += z;
    }
    return sum;
}

int main(int , char* argv[]) {
    int result = work(*argv[1], *argv[2]);
    return result;
}

でコンパイルすると、このプログラムの実行に 0.38 秒かかり、または-Osでコンパイルすると 0.44 秒かかります。これらの時間は一貫して得られ、実質的にノイズはありません (gcc 4.7.2、x86_64 GNU/Linux、Intel Core i5-3320M)。-O2-O3

(更新:アセンブリコードはすべてGitHubfno-align-*: 投稿が肥大化し、フラグが同じ効果を持つため、質問にほとんど価値を追加していないようです。

生成されたアセンブリは次のとおりです。-Osそして-O2

残念ながら、アセンブリに関する私の理解は非常に限られているため、次にやったことが正しかったかどうかはわかりません。アセンブリを取得し、行を除いて-O2すべての違いをアセンブリにマージしました。結果-Os .p2alignこここのコードは依然として 0.38 秒で実行され、唯一の違いはその .p2align 内容です。

私の推測が正しければ、これらはスタックの整列のためのパディングです。GCC が NOP で関数を埋め込むのはなぜですか?これは、コードの実行速度が速くなることを期待して行われますが、どうやらこの最適化は私のケースでは裏目に出たようです。

この場合、パディングが原因なのでしょうか? なぜ、どのように?

それが出すノイズのせいで、タイミングのマイクロ最適化はほぼ不可能になります。

C または C++ ソース コードでマイクロ最適化 (スタック アライメントとは無関係) を実行するときに、このような偶発的な幸運な / 不運なアライメントが干渉しないようにするにはどうすればよいでしょうか。


アップデート:

続くパスカル・クオックの回答私はアラインメントを少しいじりました。gcc-O2 -fno-align-functions -fno-align-loopsに渡すことで、.p2alignアセンブリからすべてがなくなり、生成された実行ファイルは0.38秒で実行されます。gcc ドキュメント:

-Os はすべての -O2 最適化を有効にします [ただし] -Os は次の最適化フラグを無効にします:

  -falign-functions  -falign-jumps  -falign-loops
  -falign-labels  -freorder-blocks  -freorder-blocks-and-partition
  -fprefetch-loop-arrays

つまり、これは(不正確な)位置合わせの問題のように思えます。

私はまだ懐疑的-march=nativeですマラト・ドゥカンの回答。これが(不正確な)位置合わせの問題に干渉しているだけではないということには納得がいきません。私のマシンにはまったく影響がありません。(それでも、私は彼の回答に賛成票を投じました。)


更新2:

-Osこれを図から取り出すことができます。以下の時間は、

  • -O2 -fno-omit-frame-pointer0.37秒

  • -O2 -fno-align-functions -fno-align-loops0.37秒

  • -S -O2その後、 0.37秒add()後に手動でアセンブリを移動しますwork()

  • -O20.44秒

add()呼び出しサイトからのの距離が非常に重要であるように思われます。 を試してみましたが、とperfの出力はほとんど意味をなさないようです。ただし、一貫した結果は 1 つしか得られませんでした。perf statperf report

-O2:

 602,312,864 stalled-cycles-frontend   #    0.00% frontend cycles idle
       3,318 cache-misses
 0.432703993 seconds time elapsed
 [...]
 81.23%  a.out  a.out              [.] work(int, int)
 18.50%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦       return x + y;
100.00 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   }
       ¦   ? retq
[...]
       ¦            int z = add(x, y);
  1.93 ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 79.79 ¦      add    %eax,%ebx

のためにfno-align-*

 604,072,552 stalled-cycles-frontend   #    0.00% frontend cycles idle
       9,508 cache-misses
 0.375681928 seconds time elapsed
 [...]
 82.58%  a.out  a.out              [.] work(int, int)
 16.83%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦       return x + y;
 51.59 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   }
[...]
       ¦    __attribute__((noinline))
       ¦    static int work(int xval, int yval) {
       ¦        int sum(0);
       ¦        for (int i=0; i<LOOP_BOUND; ++i) {
       ¦            int x(xval+sum);
  8.20 ¦      lea    0x0(%r13,%rbx,1),%edi
       ¦            int y(yval+sum);
       ¦            int z = add(x, y);
 35.34 ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 39.48 ¦      add    %eax,%ebx
       ¦    }

のために-fno-omit-frame-pointer

 404,625,639 stalled-cycles-frontend   #    0.00% frontend cycles idle
      10,514 cache-misses
 0.375445137 seconds time elapsed
 [...]
 75.35%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]                                                                                     ¦
 24.46%  a.out  a.out              [.] work(int, int)
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
 18.67 ¦     push   %rbp
       ¦       return x + y;
 18.49 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   const int LOOP_BOUND = 200000000;
       ¦
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦     mov    %rsp,%rbp
       ¦       return x + y;
       ¦   }
 12.71 ¦     pop    %rbp
       ¦   ? retq
 [...]
       ¦            int z = add(x, y);
       ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 29.83 ¦      add    %eax,%ebx

add()遅いケースでは、呼び出しが遅れているようです。

私は、上記に示した統計情報だけでなく、自分のマシンで出力できるものすべてを調べました。perf -e

同じ実行可能ファイルの場合、stalled-cycles-frontend実行時間との線形相関が見られます。これほど明確に相関するものは他には見当たりませんでした。(stalled-cycles-frontend異なる実行可能ファイルを比較しても意味がありません。)

最初のコメントとして出てきたキャッシュ ミスも含めました。私は、上記に示したものだけでなく、自分のマシンで測定できるすべてのキャッシュ ミスを調べましたperf。キャッシュ ミスは非常にノイズが多く、実行時間との相関はほとんどまたはまったくありません。

ベストアンサー1

デフォルトでは、コンパイラは「平均的な」プロセッサ向けに最適化します。プロセッサごとに異なる命令シーケンスが優先されるため、 で有効にされたコンパイラ最適化は-O2平均的なプロセッサにはメリットがありますが、特定のプロセッサではパフォーマンスが低下する可能性があります ( にも同じことが当てはまります)。同じ例を異なるプロセッサで試してみると、一部のプロセッサでは のメリットがあり、他のプロセッサでは の最適化の方が有利である-Osことがわかります。-O2-Os

time ./test 0 0複数のプロセッサでの結果は次のとおりです(ユーザー時間が報告されています)。

Processor (System-on-Chip)             Compiler   Time (-O2)  Time (-Os)  Fastest
AMD Opteron 8350                       gcc-4.8.1    0.704s      0.896s      -O2
AMD FX-6300                            gcc-4.8.1    0.392s      0.340s      -Os
AMD E2-1800                            gcc-4.7.2    0.740s      0.832s      -O2
Intel Xeon E5405                       gcc-4.8.1    0.603s      0.804s      -O2
Intel Xeon E5-2603                     gcc-4.4.7    1.121s      1.122s       -
Intel Core i3-3217U                    gcc-4.6.4    0.709s      0.709s       -
Intel Core i3-3217U                    gcc-4.7.3    0.708s      0.822s      -O2
Intel Core i3-3217U                    gcc-4.8.1    0.708s      0.944s      -O2
Intel Core i7-4770K                    gcc-4.8.1    0.296s      0.288s      -Os
Intel Atom 330                         gcc-4.8.1    2.003s      2.007s      -O2
ARM 1176JZF-S (Broadcom BCM2835)       gcc-4.6.3    3.470s      3.480s      -O2
ARM Cortex-A8 (TI OMAP DM3730)         gcc-4.6.3    2.727s      2.727s       -
ARM Cortex-A9 (TI OMAP 4460)           gcc-4.6.3    1.648s      1.648s       -
ARM Cortex-A9 (Samsung Exynos 4412)    gcc-4.6.3    1.250s      1.250s       -
ARM Cortex-A15 (Samsung Exynos 5250)   gcc-4.7.2    0.700s      0.700s       -
Qualcomm Snapdragon APQ8060A           gcc-4.8       1.53s       1.52s      -Os

場合によっては、gcc特定のプロセッサ向けに最適化するように要求することで、不利な最適化の影響を軽減できます (オプション-mtune=nativeまたはを使用-march=native)。

Processor            Compiler   Time (-O2 -mtune=native) Time (-Os -mtune=native)
AMD FX-6300          gcc-4.8.1         0.340s                   0.340s
AMD E2-1800          gcc-4.7.2         0.740s                   0.832s
Intel Xeon E5405     gcc-4.8.1         0.603s                   0.803s
Intel Core i7-4770K  gcc-4.8.1         0.296s                   0.288s

更新: Ivy Bridge ベースの Core i3 では、gcc( 4.6.4、、 ) の 3 つのバージョン4.7.34.8.1パフォーマンスが大きく異なるバイナリが生成されますが、アセンブリ コードにはわずかな違いしかありません。これまでのところ、この事実について説明はありません。

アセンブリ元gcc-4.6.4 -Os(実行時間 0.709 秒):

00000000004004d2 <_ZL3addRKiS0_.isra.0>:
  4004d2:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004d5:       c3                      ret

00000000004004d6 <_ZL4workii>:
  4004d6:       41 55                   push   r13
  4004d8:       41 89 fd                mov    r13d,edi
  4004db:       41 54                   push   r12
  4004dd:       41 89 f4                mov    r12d,esi
  4004e0:       55                      push   rbp
  4004e1:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  4004e6:       53                      push   rbx
  4004e7:       31 db                   xor    ebx,ebx
  4004e9:       41 8d 34 1c             lea    esi,[r12+rbx*1]
  4004ed:       41 8d 7c 1d 00          lea    edi,[r13+rbx*1+0x0]
  4004f2:       e8 db ff ff ff          call   4004d2 <_ZL3addRKiS0_.isra.0>
  4004f7:       01 c3                   add    ebx,eax
  4004f9:       ff cd                   dec    ebp
  4004fb:       75 ec                   jne    4004e9 <_ZL4workii+0x13>
  4004fd:       89 d8                   mov    eax,ebx
  4004ff:       5b                      pop    rbx
  400500:       5d                      pop    rbp
  400501:       41 5c                   pop    r12
  400503:       41 5d                   pop    r13
  400505:       c3                      ret

アセンブリ元gcc-4.7.3 -Os(実行時間 0.822 秒):

00000000004004fa <_ZL3addRKiS0_.isra.0>:
  4004fa:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004fd:       c3                      ret

00000000004004fe <_ZL4workii>:
  4004fe:       41 55                   push   r13
  400500:       41 89 f5                mov    r13d,esi
  400503:       41 54                   push   r12
  400505:       41 89 fc                mov    r12d,edi
  400508:       55                      push   rbp
  400509:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  40050e:       53                      push   rbx
  40050f:       31 db                   xor    ebx,ebx
  400511:       41 8d 74 1d 00          lea    esi,[r13+rbx*1+0x0]
  400516:       41 8d 3c 1c             lea    edi,[r12+rbx*1]
  40051a:       e8 db ff ff ff          call   4004fa <_ZL3addRKiS0_.isra.0>
  40051f:       01 c3                   add    ebx,eax
  400521:       ff cd                   dec    ebp
  400523:       75 ec                   jne    400511 <_ZL4workii+0x13>
  400525:       89 d8                   mov    eax,ebx
  400527:       5b                      pop    rbx
  400528:       5d                      pop    rbp
  400529:       41 5c                   pop    r12
  40052b:       41 5d                   pop    r13
  40052d:       c3                      ret

アセンブリ元gcc-4.8.1 -Os(実行時間 0.994 秒):

00000000004004fd <_ZL3addRKiS0_.isra.0>:
  4004fd:       8d 04 37                lea    eax,[rdi+rsi*1]
  400500:       c3                      ret

0000000000400501 <_ZL4workii>:
  400501:       41 55                   push   r13
  400503:       41 89 f5                mov    r13d,esi
  400506:       41 54                   push   r12
  400508:       41 89 fc                mov    r12d,edi
  40050b:       55                      push   rbp
  40050c:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  400511:       53                      push   rbx
  400512:       31 db                   xor    ebx,ebx
  400514:       41 8d 74 1d 00          lea    esi,[r13+rbx*1+0x0]
  400519:       41 8d 3c 1c             lea    edi,[r12+rbx*1]
  40051d:       e8 db ff ff ff          call   4004fd <_ZL3addRKiS0_.isra.0>
  400522:       01 c3                   add    ebx,eax
  400524:       ff cd                   dec    ebp
  400526:       75 ec                   jne    400514 <_ZL4workii+0x13>
  400528:       89 d8                   mov    eax,ebx
  40052a:       5b                      pop    rbx
  40052b:       5d                      pop    rbp
  40052c:       41 5c                   pop    r12
  40052e:       41 5d                   pop    r13
  400530:       c3                      ret

おすすめ記事