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


445

私が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;
}

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

(更新:すべてのアセンブリコードをGitHubに移動しましたfno-align-*フラグが同じ効果を持つため、投稿が肥大化し、質問にはほとんど価値がありません。)

ここでアセンブリが生成される-Osと、-O2

残念ながら、アセンブリについての私の理解は非常に限られているため、次に何をしたのかがわかりません。アセンブリを取得し、-O2すべての違いを行-Os 以外のアセンブリにマージしまし。このコードはまだ0.38秒で実行され、唯一の違いはものです。.p2align .p2align

私が正しく推測すれば、これらはスタックアライメントのパディングです。によるなぜのNOPとGCCパッド機能していますか?コードがより高速に実行されることを期待して行われますが、私の場合は明らかにこの最適化が裏目に出ました。

この場合の原因はパディングですか?なぜ、どうやって?

それによって生じるノイズは、タイミングのマイクロ最適化を不可能にします。

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


更新:

Pascal Cuoqの答えに続いて、私は調整を少しいじりました。-O2 -fno-align-functions -fno-align-loopsgccに渡すことで、すべて.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-pointer 0.37秒

  • -O2 -fno-align-functions -fno-align-loops 0.37秒

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

  • -O2 0.44秒

add()コールサイトからの距離がとても重要だと私には思えます。私は試しましたperfが、の出力perf statperf report私にはほとんど意味がありません。しかし、私はそれから1つの一貫した結果しか得ることができませんでした:

-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上で与えられたものだけでなく、私のマシンでによって測定できるすべてのキャッシュミスを調べました。キャッシュミスは非常にノイズが多く、実行時間との相関はほとんどありません。


36
ブラインド推測:これはキャッシュミスになる可能性がありますか?

@ H2CO3これも私の最初の考えでしたが、OPの質問をよく読んで理解せずにコメントを投稿するには十分ではありませんでした。
πάνταῥεῖ

2
@ g-makulikそれが私がそれが「盲目の推測」であると警告した理由です;-)「TL; DR」は悪い質問のために予約されています。:P

3
興味深いデータポイント:OS Xでclangを使用してこれをコンパイルすると、-O3または-Ofastが-Osの約1.5倍高速であることがわかります(gccでの再現は試していません)
Rob Napier

2
同じコードです。.L3のアドレスをよく見てください。整列されていないブランチターゲットは高価です。
ハンス

回答:


503

デフォルトでは、コンパイラーは「平均的な」プロセッサー向けに最適化します。異なるプロセッサは異なる命令シーケンスを優先するため、によって有効にされるコンパイラの最適化は、-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

更新:アイビーブリッジベースのコアには、3つのバージョンi3をgcc4.6.44.7.3および4.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

186
明確にするために:12の異なるプラットフォームでOPのコードのパフォーマンスを実際に測定しましたか?(+1に、あなたがそうするだろうと思っただけです)
anatolyg 2013年

194
@anatolygはい、そうしました!(およびすぐにいくつか追加されます)
Marat Dukhan 2013年

43
確かに。異なるCPUについて理論化するだけでなく、実際にそれを証明するためのもう1つの+1 。速度に関するすべての答えに、何か(alas)はありません。これらのテストは同じOSで実行されますか?(これにより結果が
歪む

7
@Ali On AMD-FX 6300 -O2 -fno-align-functions -fno-align-loopsは時間を0.340sに落とすため、アラインメントで説明できます。ただし、最適な配置はプロセッサに依存します。一部のプロセッサは、配置されたループと関数を優先します。
Marat Dukhan、2013年

13
@Jongware OSが結果に大きく影響する方法はわかりません。ループがシステムコールを行うことはありません。
2013年

186

同僚は私の質問に対するもっともらしい答えを見つけるのを手伝ってくれました。彼は256バイト境界の重要性に気づきました。彼はここに登録されていないので、自分で回答を投稿する(そしてすべての名声を獲得する)ように勧めました。


簡潔な答え:

この場合の原因はパディングですか?なぜ、どうやって?

結局、それはすべて整列に要約されます。アラインメントはパフォーマンスに大きな影響を与える可能性があるため-falign-*、最初にフラグを設定しました。

(偽ですか?)バグレポートをgcc開発者に提出しまし。デフォルトの動作は「ループをデフォルトで8バイトに揃えますが、10バイトを超える値を入力する必要がない場合は、16バイトに揃えようとする」ことがわかりました。どうやら、この特定のケースや私のマシンでは、このデフォルトは最良の選択ではありません。Clang 3.4(トランク)-O3は適切な配置を行い、生成されたコードはこの奇妙な動作を示しません。

もちろん、不適切な調整が行われると、事態はさらに悪化します。不必要な/悪いアライメントは理由もなくバイトを消費し、潜在的にキャッシュミスなどを増加させます。

それによって生じるノイズは、タイミングのマイクロ最適化を不可能にします。

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

単にgccに正しい配置を行うように指示するだけです。

g++ -O2 -falign-functions=16 -falign-loops=16


長い答え:

次の場合、コードの実行速度が低下します。

  • XXバイト境界カットadd()途中で(XXマシン依存します)。

  • への呼び出しがバイト境界add()を飛び越える必要がXXあり、ターゲットが整列されていない場合。

  • add()整列していない場合 。

  • ループが整列していない場合。

最初の2つは、Marat Dukhanが親切に投稿したコードと結果に美しく表示されます。この場合、gcc-4.8.1 -Os(0.994秒で実行されます):

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

256バイトの境界add()が真ん中をカットしadd()、ループも整列もされていません。驚き、驚き、これは最も遅いケースです!

場合gcc-4.7.3 -Os(0.822秒で実行される)、コールド部に256バイト境界のみ切り込み(どちらもループもadd()あるカット)。

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

[...]

  40051a:       e8 db ff ff ff          call   4004fa <_ZL3addRKiS0_.isra.0>

何も整列されておらず、への呼び出しadd()は256バイト境界を飛び越える必要があります。このコードは2番目に遅いです。

場合はgcc-4.6.4 -Os何も揃っていないが(0.709秒で実行)、への呼び出しはadd()256バイト境界を飛び越えるする必要はありませんし、ターゲットが正確に32バイト先です。

  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>

これは3つすべての中で最速です。彼のマシンで256バイトの境界が特別である理由は、それを理解するために彼に任せます。私はそのようなプロセッサを持っていません。

今、私のマシンでは、この256バイトの境界効果はありません。私のマシンでは、関数とループ調整のみが実行されます。合格するとg++ -O2 -falign-functions=16 -falign-loops=16、すべてが正常に戻ります。常に最速のケースが得られ、時間は-fno-omit-frame-pointerフラグの影響を受けなくなります。私g++ -O2 -falign-functions=32 -falign-loops=32は16の倍数または任意の数を渡すことができますが、コードはそれに敏感ではありません。

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

おそらく説明は、この例のように、配置に敏感なホットスポットがあったことです。フラグをいじる(の-Os代わりに渡す-O2)ことにより、これらのホットスポットは偶然に幸運な方法で配置され、コードが高速になりました。サイズの最適化とは何の関係もありませんでした。これらは、ホットスポットがより適切に配置されたという偶然によるものでした。これからは、プロジェクトに対するアライメントの影響を確認します。

ああ、もう1つ。例に示すように、このようなホットスポットはどのようにして発生しますか?add()失敗のようなこのような小さな関数のインライン化はどのようにできますか?

このことを考慮:

// add.cpp
int add(const int& x, const int& y) {
    return x + y;
}

別のファイルで:

// main.cpp
int add(const int& x, const int& y);

const int LOOP_BOUND = 200000000;

__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;
}

次のようにコンパイルされますg++ -O2 add.cpp main.cpp

      gccはインライン化されませんadd()

それだけです。OPのようなホットスポットを意図せずに作成するのは簡単です。もちろん、それは私のせいです。gccは優れたコンパイラです。上記を次のようにコンパイルした場合g++ -O2 -flto add.cpp main.cpp、つまり、リンク時の最適化を実行すると、コードは0.19秒で実行されます。

(インライン化はOPで人為的に無効にされているため、OPのコードは2倍遅くなりました)。


19
うわー...これは間違いなく、私が異常のベンチマークを回避するために通常行うことを超えています。
Mysticial 2013年

@アリ私はコンパイラが見えないものをどのようにインライン化できるので、それは理にかなっていると思いますか?これinlineが、ヘッダーで+関数定義を使用する理由です。GCCでのltoの成熟度がわかりません。少なくともmingwでの私の経験はヒットまたはミスです。
greatwolf 2013年

7
異なるサイズのLinux環境を使用してバイナリイメージ全体を一度に1バイトずつシフトしながら、かなり大きなアプリケーション(perl、Spiceなど)を実行することについての記事が数年前にあったのは、ACMの通信だと思います。典型的な分散は15%程度です。彼らの要約によれば、このアライメントの外部変数が考慮されていないため、多くのベンチマーク結果は役に立たないということです。
Gene

1
特に-flto。経験から言えば、これまでに使用したことがないのであれば、それはかなり革命的です:)
underscore_d

2
これは、アライメントがパフォーマンスに影響を与えるとどのようにする方法についての交渉はそれをプロファイルすることは素晴らしいビデオです:youtube.com/watch?time_continue=1&v=r-TLSBdHe1A
Zhro

73

プログラムの全体的なパフォーマンス(大規模なものを含む)に対するアラインメントの影響が調査されたことを指摘するために、この事後承認を追加します。たとえば、この記事(およびこのバージョンもCACMに表示されたと思います)は、リンクの順序とOS環境のサイズの変更だけで、パフォーマンスを大幅にシフトできることを示しています。彼らはこれを「ホットループ」の整列に起因すると考えています。

この論文は、「明らかに悪いことは何もせずに、間違ったデータを作成する」というタイトルの論文です。は、プログラム実行環境のほぼ制御不可能な違いによる不注意な実験的バイアスが、多くのベンチマーク結果をおそらく意味のないものにしていると述べています。

同じ観察で別の角度に遭遇していると思います。

パフォーマンスが重要なコードの場合、これは、インストール時または実行時に環境を評価し、主要なルーチンのさまざまに最適化されたバージョンの中からローカルの最適なものを選択するシステムにとってかなり良い議論です。


33

あなたはあなたがしたことと同じ結果を得ることができると思います:

-O2のアセンブリを取得し、.p2align行以外のすべての違いを-Oのアセンブリにマージしました。

…を使用する-O2 -falign-functions=1 -falign-jumps=1 -falign-loops=1 -falign-labels=1。私はこれらのオプションですべてをコンパイルしており、プレーンよりも高速でした-O2、15年間、測定するたびにでした。

また、完全に異なるコンテキスト(異なるコンパイラを含む)の場合も、状況が似いることに気付きました。「速度ではなくコードサイズを最適化する」オプションがコードサイズと速度を最適化します。

私が正しく推測すれば、これらはスタックアライメントのパディングです。

いいえ、これはスタックとは何の関係もありません。デフォルトで生成されるNOPと、オプション-falign-* = 1 preventはコードアライメント用です。

なぜGCCパッドはNOPで機能するのですか?コードがより高速に実行されることを期待して行われますが、私の場合は明らかにこの最適化が裏目に出ました。

この場合の原因はパディングですか?なぜ、どうやって?

パディングが原因である可能性が非常に高いです。パディングが必要であると感じられ、場合によっては有用である理由は、コードが通常16バイトの行でフェッチされるためです(詳細については、Agner Fogの最適化リソースを参照してください。詳細はプロセッサーのモデルによって異なります)。関数、ループ、またはラベルを16バイト境界に揃えると、関数またはループを含めるために必要な行が1行少なくなる可能性が統計的に増加します。これらのNOPによりコード密度が低下し、キャッシュ効率が低下するため、明らかに逆効果になります。ループとラベルの場合、NOPは1回実行する必要がある場合があります(ジャンプからではなく、ループ/ラベルに実行が到着したとき)。


面白いのは、-O2 -fno-omit-frame-pointerと同じくらい良い-Osです。更新された質問を確認してください。
Ali

11

プログラムがCODE L1キャッシュによって制限されている場合、サイズの最適化が突然成果を上げ始めます。

最後にチェックしたとき、コンパイラはすべてのケースでこれを理解するのに十分スマートではありません。

あなたの場合、-O3はおそらく2つのキャッシュラインに十分なコードを生成しますが、-Osは1つのキャッシュラインに適合します。


1
これらのalign =パラメータがキャッシュラインのサイズにどれだけ関連していると思いますか?
ジョシュア

もう気になりません。私のマシンには表示されません。-falign-*=16フラグを渡すことで、すべてが正常に戻り、すべてが一貫して動作します。私に関する限り、この質問は解決されています。
アリ

7

私は決してこの分野の専門家ではありませんが、分岐予測に関しては、現代のプロセッサは非常に敏感であることを覚えているようです。分岐を予測するために使用されるアルゴリズムは、ターゲットの距離や方向など、コードのいくつかのプロパティに基づいています(または、少なくともアセンブラコードを作成した当時は)。

頭に浮かぶシナリオは小さなループです。ブランチが後方に移動し、距離がそれほど遠くない場合、すべての小さなループがこのように行われるため、ブランチの予測はこのケースに最適化されていました。生成されたコードの位置を入れ替えたり、両方の位置を少し変更したりするaddwork、同じルールが適用される場合があります。

とはいえ、それを確認する方法がわからないので、これが調査対象の可能性があることをお知らせしたいと思います。


ありがとう。私はそれで遊ん:私だけ交換してスピードアップを取得add()してwork()いる場合-O2渡されます。他のすべての場合では、スワッピングによってコードが大幅に遅くなります。週末の間、私はブランチ予測/誤予測統計も分析しましたperfしましたが、この奇妙な振る舞いを説明できるものは何もありませんでした。唯一の一貫した結果は、低速の場合、ループ内の呼び出し直後の行にperf100.0インチadd()と大きな値が報告さadd()れることです。なんらかの理由add()で、低速の場合は停止していますが、高速の場合は停止しているようです。
アリ

私は自分のマシンの1つにインテルのVTuneをインストールし、自分でプロファイリングすることを考えています。perf限られた数のものだけをサポートします。おそらくIntelのものは、自分のプロセッサでもう少し便利です。
アリ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.