コンパイラの最適化の罪

C言語のコンパイラ設定の最適化について

 

プログラム内容は正しいのに、どうしても不正な動作する・・
これも設定に潜み(ひそみ)ときどきハマリ込むトラブル
 [勝手にされる最適化]です。

コンパイラの最適化


コンパイラの最適化とは

 int i;
 i = 1;
 i = 2;
 printf( "i = %d", i );    

 

  みたいなことをやると 当然 i の表示は 2となります。

 

最適化とは 途中の


i = 1;   は次に書き換えられるので 無駄と判断して 
i = 1;   をコンパイラが気を利かせて勝手にカットしてしまう

 

というのがコンパイラの最適化なワケなのですが、このお節介な最適化も時々ハマり込むワナになってます。

 

i = 1;
i = 2;
printf( "i = %d", i );

 

こういう変数への入力は パソコンソフトなどの場合だと どう見ても途中はムダに見えますが、、

 

これは組み込みマイコンになると

 

 PORTA = 1;
 PORTA = 2;


というのには意味があったりします。( PORTは通常最適化防止になっているけれど)

 

ポートAの BIT0 を HIGHに 
次は ポートAの BIT1 を HIGHに  したいという場合など。。


上の  i = 1; を最適化で気を利かされ省略されるならまだいいのですが、

こういう


PORTA = 1;
 for( j = 0; j < 2000; j++);  
PORTA = 2; 

PORTA = 1;
 for( j = 0; j < 2000; j++);
PORTA = 2;

 

なんて内容にはとても意味があります。

 

何やりたいのか 人が見たらなんとなくわかりそうなものですが、、

 

    for( j = 0; j < 2000; j++);

 

までも 豪快にもすっ飛ばしてくれちゃう最適化があります。。 

 

空に回っているだけなので for( j = 0; j < 2000; j++); もムダとされる

 

実際の実行内容は 
 PORTA = 1;
 PORTA = 2;
になり。。。

 

なんでかいろいろ変更しても いっこうに

 待ち時間が出ないぞ、 LED点滅しないぞ? となって、

 

原因がコンパイラの最適化だったりします。

 

またシミュレータのデバッグで 変更確認の試しに 事前に変数に値を入れて
 count = 12;
 count = PORTA;

も 同様に最適化で count = 12; カットされて、実行されなかったりします。

 

コンパイラをかえて違う処理系などに移ったときにわかりにくいところです。。

 

コンパイラの最適化 防止

最適化をほどほどにするには コンパイラのオプションでの最適化レベルの設定でやりますが、調べる必要があり、なかなか設定もややこしいです。

 

それで、

 この変数には 一切最適化をしてくれるな

 

という場合に
 volatile を付けて最適化防止ができます。


 volatile int i , j;      //  i , j 最適化防止
 volatile long count;    //よくわからない変数だけど これもvolatileなのじゃ   
PORTA = 1;
 for( j = 0; j<2000; j++);
PORTA = 2;

 

と ハマリ込んだ後にはやたらと volatile がいっぱい付いたソースが出来上がる・・・


 それは コンパイラ最適化の罪かもしれません。。

 

*GCC コンパイルオプションについて

 

AVR Studio の元設定では 最適化レベルが -Os になっていて豪快に最適化してくれちゃいます。通常 -O0 でいいでしょう。

 


GCC最適化オプションについて

 

 以下のオプションは色々な種類の最適化を制御する。
 -O

 

-O1

最適化を行なう。最適化を行なうコンパイルには幾らか余計に時間がかかり、大きな関数についてはたくさんのメモリを余計に使う。

-O を指定しない場合は、コンパイラの目標はコンパイルのコストを小さくすることとデバッグが期待どおりの結果にすることである。各文は独立している。文と文の間にブレークポイントを設定してプログラムを止めたとき、どの変数にも新たな値を代入できるし、プログラムカウンタを変更して、その関数内の他のどの文にも飛ばすことができ、そしてソースコードから期待できる結果と全く同じものが得られる。

 -O を指定しないと、register 宣言した変数しかレジスタに割り当てない。コンパイル結果のコードは、-O なしの PCC で作られるよりもやや悪い。

-O を指定すると、コードサイズと実行時間を小さくしようとする。

-O を指定すると、全機種で -fthread-jumps と-fdefer-pop を有効にする。遅延スロットのある機種では-fdelayed-branch をオンにし、フレームポインタなしでもデバッグをサポートできる機種では -fomit-frame-pointer をオンにする。機種によっては、他のオプションをオンにするものもある。

-O2
さらなる最適化を行なう。GCC は、スペース-速度のトレードオフを含まないほとんど全ての最適化を実行する。-O2 を指定した場合は、ループ展開や関数のインライン展開を行なわない。-O と比べると、このオプションはコンパイル時間が増え、生成コードの効率が良くなる。

-O2 を指定すると、ループ展開と関数のインライン展開、それに厳密なエイリアシングを除いた全ての最適化が有効になる。また、全ての機種で -fforce-mem オプションを付け、デバッグと干渉しない機種ではフレームポインタの削除を行なう。

-O3
さらに最適化を行なう。-O3 は -O2 で指定される全ての最適化を行ない、かつ inline-functions オプションを行なう。
-O0
最適化を行なわない。
-Os

サイズについて最適化する。-Os を指定すると、普通はコードサイズを大きくしない -O2 の最適化を全て有効にする。また、コードサイズを小さくするよう設計されたさらなる最適化を実行する。

 -O オプションを複数指定した場合は、最適化レベルの数字が付いていてもいなくても、最後に指定したオプションが有効

 

(GCC gcc-2.95.3 マニュアル 日本語訳より)


 

  • facebook
  • twtter
  • google+
  • hatena