GCCコンパイラが一部のコードを省略しているのはなぜですか?


9

GCCコンパイラが近隣のまったく同じコードを保持しながら、なぜコードの一部を切り取るのか理解できません。

Cコード:

#define setb_SYNCO do{(PORTA|= (1<<0));} while(0);

ISR(INT0_vect){
    unsigned char i;

    i = 10;
    while(i>0)i--;   // first pause - omitted

    setb_SYNCO;
    setb_GATE;
    i=30;
    clrb_SYNCO;
    while(i>0)i--;  // second pause - preserved
    clrb_GATE;
}

LSSの対応する部分(コンパイラーによって作成されたアセンブラーファイル):

ISR(INT0_vect){
  a4:   1f 92           push    r1
  a6:   0f 92           push    r0
  a8:   0f b6           in  r0, 0x3f    ; 63
  aa:   0f 92           push    r0
  ac:   11 24           eor r1, r1
  ae:   8f 93           push    r24
    unsigned char i;

    i = 10;
    while(i>0)i--;

    setb_SYNCO;
  b0:   d8 9a           sbi 0x1b, 0 ; 27
    setb_GATE;
  b2:   d9 9a           sbi 0x1b, 1 ; 27
    i=30;
    clrb_SYNCO;
  b4:   d8 98           cbi 0x1b, 0 ; 27
  b6:   8e e1           ldi r24, 0x1E   ; 30
  b8:   81 50           subi    r24, 0x01   ; 1
    while(i>0)i--;
  ba:   f1 f7           brne    .-4         ; 0xb8 <__vector_1+0x14>
    clrb_GATE;
  bc:   d9 98           cbi 0x1b, 1 ; 27
}
  be:   8f 91           pop r24
  c0:   0f 90           pop r0
  c2:   0f be           out 0x3f, r0    ; 63
  c4:   0f 90           pop r0
  c6:   1f 90           pop r1
  c8:   18 95           reti

私はコンパイラがそのようなコードがダミーであることを理解し、それを切り取っていると仮定することができますが、なぜコードの最後に同じものを保持するのですか?

そのような最適化を防ぐためのコンパイラー指示はありますか?


1
また、単一の関数を最適化しないようにコンパイラーに指示することもできます。ISRでこのメソッドを試す価値があるかもしれません。stackoverflowでこの質問を参照してください。
Vladimir Cravero 2015年

2
こんにちは、私はあなたの質問にatmegaを削除するために "c"タグを追加しました。制限があるため(5つ)、タグを1つ削除する必要がありました。また、コード関連の質問をするときに、言語名をタグとして追加すると、すべてのコード(Q&A)が強調表示されるため、すばらしいです。
Vladimir Cravero

5
一般的に言って、高レベル言語(Cなど)は、結果のアセンブリとの1対1の関係に拘束されないように明示的に設計されてます。タイミングを正しくするために指示を数える必要がある場合は、常にアセンブリに依存する必要があります(一部の回答と同様)。高水準言語の要点は、コンパイラーがコードを以前よりも速く、より強くするのを助ける自由があるということです。レジスター割り当てや分岐予測などの詳細は、コンパイラーに任せるほうがはるかに適切です...このような、プログラマーであるユーザーが必要な命令を正確に知っている場合を除きます。
Cort Ammon、2015年

5
より良い質問は、なぜGCC はこれらのループの両方を最適化しないのですか?
Ilmari Karonen

5
Gccは最初にループを展開してから、対応するコードが役に立たないことに気付きます。ループサイズが30の場合、アンロールは愚かで、gccはそれを行いません。より高い最適化レベルでは、両方が最適化されます。
Marc Glisse、2015年

回答:


9

1つのコメントで「各CPUティックは価値がある」と述べているので、インラインアセンブリを使用して、遅延を希望どおりにループさせることをお勧めします。このソリューションは、さまざまなソリューションより優れているvolatile-O0、または意図が明確になるためです。

unsigned char i = 10;
__asm__ volatile ( "loop: subi    %0, 0x01\n\t"
                   "      brne    loop"
                   : "+rm" (i)
                   : /* no inputs */
                   : /* no dirty registers to decleare*/);

これでうまくいくはずです。不安定なのは、コンパイラに「これは何も実行しないことはわかっています。そのままにして信頼してください」と伝えることです。3つのasm "ステートメント"は非常に自明です。r24の代わりに任意のレジスタを使用できます。コンパイラは低いレジスタが好きなので、高いレジスタを使用したいと思うかもしれません。最初の後に:出力(読み取りと書き込み)c変数:をリストする必要がありますが、2番目の後に入力(ronly)c変数をリストする必要があります。また、何もありません。3番目のパラメーターは変更されたレジスターのコンマ区切りリストです。 、この場合はr24。ZEROもちろんフラグが変わるので、ステータスレジスタも含める必要があるかどうかはわかりませんが、含めていません。

OPの要求に応じて編集された回答を編集します。いくつかのメモ。

"+rm"(i)あなたがコンパイラをさせるされていることを意味はIに配置することを決定したm個のエモリーまたはでのR egister。コンパイラーがフリーの場合、コンパイラーはより適切に最適化できるため、ほとんどの場合これは良いことです。あなたの場合、私はあなたがレジスターになるように強制するためにr制約だけを保持したいと思います。


これは私が本当に必要なもののようです。しかし、元の回答で述べたcリテラルで10はなく、変数を受け入れるように回答を変更できますか?私はasm構造の適切な使用に関してGCCマニュアルを読もうとしていますが、今は少しわかりにくくなっています。よろしくお願いします!
Roman Matveev

1
リクエストに応じて編集された@RomanMatveev
Vladimir Cravero

13

ループで実際に何かを実行してみることができます。現状では、コンパイラは「このループは何も実行していない-私はそれを取り除く」と言っています。

したがって、私が頻繁に使用する構成を試すことができます。

int i;
for (i = 0; i < 10; i++) {
    asm volatile ("nop");
}

注:gccコンパイラーのすべてのターゲットが同じインラインアセンブリ構文を使用するわけではありません。ターゲットに合わせて微調整する必要がある場合があります。


あなたの解決策は私のものよりもはるかにエレガントに見えます...おそらく、正確なサイクル数が必要な場合、私のものはより優れていますか?つまり、forの全体が特定の方法でコンパイルされることが保証されていませんね。
Vladimir Cravero 2015年

8
Cを使用しているという単純な事実は、サイクルを保証できないことを意味します。正確なサイクルカウントが必要な場合は、ASMが唯一の方法です。あなたは-funroll-ループなど、あなたがいない場合よりも有効になっている場合、私は意味、あなたはCループと異なるタイミングを得る
Majenko

うん、そう思った。十分に高いi値(100以上)でHW遅延を実行すると、あなたのソリューションは読みやすさを向上させながら実質的に同じ結果をもたらすと思います。
Vladimir Cravero

6

はい、あなたはそれを想定することができました。変数iを揮発性として宣言する場合は、コンパイラーにiで最適化しないように指示します。


1
それは私の意見では完全に真実ではありません。
Vladimir Cravero 2015年

1
@VladimirCravero、それはどういう意味ですか?もっと明確にしていただけませんか?
Roman Matveev 2015年

2
私が言ったのは、コンパイラーが何をするかについて私はあまり確信がないということです。変数をvolatileと宣言すると、コンパイラーは別の場所で変更される可能性があるため、実際に変更する必要があります。
Vladimir Cravero 2015年

1
@Roman Matveev register unsigned char volatile i __asm__("r1");多分?
a3f 2015年

2
i揮発性として宣言すると、すべてが解決されます。これは、C標準5.1.2.3で保証されています。i揮発性の場合、適合コンパイラはこれらのループを最適化してはなりません。幸い、GCCは適合コンパイラーです。残念ながら、標準に準拠していないCコンパイラはたくさんありますが、それはこの特定の質問には関係ありません。インラインアセンブラは絶対に必要ありません。
ランディン、2015年

1

最初のループの後はi定数です。iとループの初期化は、定数値を生成するだけです。規格には、このループをそのままコンパイルする必要があるとは規定されていません。標準はタイミングについても何も述べていません。コンパイルされたコードは、ループが存在しているかのように動作する必要があります。この最適化が標準に基づいて実行されたとは確実に言えません(タイミングは考慮されません)。

2番目のループも削除する必要があります。私はそれがバグではない(または最適化が欠けている)と考えています。ループの後iは定数ゼロです。コードiをゼロに設定して置き換える必要があります。

私はGCCが続け考えるiあなたが(不透明な)ポートへのアクセスが影響を与える可能性があります実行することを理由純粋に周りi

使用する

asm volatile ("nop");

GCCをだまして、ループが何かをしていると信じ込ませます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.