web-dev-qa-db-ja.com

GCCがビジーな待機ループを最適化するのを防ぐ方法は?

Atmel AVRマイクロコントローラー用のCコードファームウェアを書きたいです。 GCCを使用してコンパイルします。また、コンパイラの最適化を有効にしたい(-Osまたは-O2)、私はそれらを有効にしない理由を見ていないので、おそらく彼らはアセンブリを手動で書くよりも速くより良いアセンブリ方法を生成します。

しかし、最適化されていない小さなコードが必要です。関数の実行をしばらく遅らせたいので、時間を無駄にするためだけに何もしないループを書きたいと思いました。正確である必要はありません。しばらくお待ちください。

/* How to NOT optimize this, while optimizing other code? */
unsigned char i, j;
j = 0;
while(--j) {
    i = 0;
    while(--i);
}

AVRのメモリアクセスは非常に遅いため、iおよびjをCPUレジスタに保持する必要があります。


更新: til/delay.h および til/delay_basic.h from AVR Libc が見つかりました。ほとんどの場合、これらの関数を使用することをお勧めしますが、この質問は引き続き有効で興味深いものです。


関連する質問:

59

dmckeeの回答 からのリンクをたどった後にこの回答を作成しましたが、彼/彼女の回答とは異なるアプローチを取ります。

関数の属性 GCCのドキュメントからの言及:

noinlineこの関数属性は、関数がインライン化の対象となるのを防ぎます。関数に副作用がない場合、関数呼び出しはライブですが、関数呼び出しが最適化されないようにするインライン化以外の最適化があります。そのような呼び出しが最適化されないようにするには、asm ("");

これにより、興味深いアイデアが得られました...内側のループにnop命令を追加する代わりに、次のように空のアセンブリコードを追加しようとしました。

_unsigned char i, j;
j = 0;
while(--j) {
    i = 0;
    while(--i)
        asm("");
}
_

そしてそれはうまくいきました!そのループは最適化されておらず、余分なnop命令は挿入されていません。

さらに、volatileを使用すると、gccはこれらの変数をRAMに保存し、lddstdの束を追加してそれらをコピーします一方、このアプローチはvolatileを使用せず、そのようなオーバーヘッドを生成しません。


更新:_-ansi_または_-std_を使用してコードをコンパイルする場合は、asmキーワードを___asm___、 GCCドキュメントで説明

さらに、 Assemblyステートメントを配置した場所で実行する必要がある場合は、__asm__ __volatile__("")を使用することもできます(つまり、ループの外に移動しないでください)最適化) .

75

iおよびj変数をvolatileとして宣言します。これにより、コンパイラはこれらの変数を含むコードを最適化できなくなります。

unsigned volatile char i, j;
21
ks1322

このアプローチが完全に見当違いであり、コンパイラのアップグレードなどによって簡単に破られることについて、まだ言及されていない理由はわかりません。目的の値を超えるまでの時間。 x86では、この目的でrdtscを使用できますが、より移植性の高い方法は、clock_gettime(または非POSIX OSのバリアント)を呼び出して時間を取得することです。現在のx86_64 Linuxは、clock_gettimeのsyscallを回避し、rdtscを内部で使用します。または、syscallのコストを処理できる場合は、最初にclock_nanosleepを使用してください...

5
R..

コンパイラのavrバージョンが フルセットの#pragmas (リンク内の興味深いものはすべてgccバージョン4.4の日付です)が、通常はここから始めます。

3
dmckee

私にとって、GCC 4.7.0では、空のasmはとにかく-O3で最適化されました(-O2で試してみました)。また、レジスターまたはvolatileでi ++を使用すると、パフォーマンスが大幅に低下します(私の場合)。

私がやったことは、コンパイラが「メインプログラム」をコンパイルするときに見ることができなかった別の空の関数とリンクしていました

基本的にこれ:

この関数を宣言して「helper.c」を作成しました(空の関数)

void donotoptimize(){}

次に、「gcc helper.c -c -o helper.o」をコンパイルしてから、

while (...) { donotoptimize();}

これは私に最高の結果をもたらしました(そして私の信念から、オーバーヘッドはまったくありませんが、私のプログラムはそれなしでは動作しないためテストできません:))

Iccでも動作するはずだと思います。リンクの最適化を有効にしているのではないかもしれませんが、gccでは有効になっています。

2
BiS

揮発性のasmを配置すると役立ちます。詳細についてはこちらをご覧ください:

http://www.nongnu.org/avr-libc/user-manual/optimization.html

Windowsで作業している場合は、以下で詳細に説明するように、プラグマの下にコードを配置することもできます。

https://www.securecoding.cert.org/confluence/display/cplusplus/MSC06-CPP.+Be+aware+of+compiler+optimization+when+dealing+with+sensitive+data

お役に立てれば。

1
Groovy

そのループを別の.cファイルに入れて、その1つのファイルを最適化しないでください。そのルーチンをアセンブラーで作成してCから呼び出すと、オプティマイザーが関与することはありません。

私は時々揮発性のことをしますが、通常、オプティマイザーがfor/whileループをタイトにするその関数への呼び出しを置くだけを返すasm関数を作成しますが、ダミー関数へのすべての呼び出しを行う必要があるため、最適化されません。 DenilsonSáからのnop答えは同じことをしますが、さらにきつい...

1
old_timer