スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。


このエントリーをはてなブックマークに追加

volatileで最適化を抑制する

プログラムは思った通りに動かない、書いた通りに動く。
(プログラミングの格言)

C言語やC++,Javaにはvolatileという修飾子があります。組み込み系ソフトウェアやマルチスレッドのアプリケーションを書いている方にとっては、なじみ深い存在ですが、そうでない方にはあまり縁がないのかもしれません。しかし、volatileの使い方や存在意義を知らないままコーディングを行うと、思わぬバグを引き起こす場合があります。今回は、そのvolatileキーワードについて簡単に説明したいと思います。

volatileは初期のCであるK&Rには含まれていませんでしたが、ANSI C(C89)以降のC標準規格にはconstと一緒に含まれるようになりました(constとvolatileをあわせてcv修飾子と呼ぶこともあります)。一般的なCなら必ず備えている修飾子です。

volatile修飾子の意味ですが、「プログラミング言語C ANSI規格準拠」によると、
volatileの目的は,黙っていると処理系で行われる最適化を抑止することにある.例えば,メモリ・マップ方式の入出力をもつマシンでは,ステータス・レジスタに対するポインタは,ポインタによる見かけ上,冗長な参照をコンパイラが除去するのを防ぐのに,volatileへのポインタと宣言することが可能である.
となっています。 なぜ最適化を抑止する必要があるのか?例をあげて説明します。

条件分削除の抑制

次のコードを見てください。変数の値が変化するまで、変数をポーリングし続けています。
extern int event_flag

void poll_event()
{
  while (event_flag == 0) {
    /* 処理。但しevent_flagは操作しない */
    ....
  }
  ....
}
whileの続行条件としてグローバル変数event_flagの値を参照していますが、ループ内部でevent_flagの値を一切処理していません。よって、単純に見ると、このwhileの条件は毎回評価する必要なく、次のような最適化が可能となります。
void poll_event()
{
  if (event_flag == 0) {
    while (1) {
      /* 処理。但しevent_flagは操作しない */
    ....
    }
  }
  ....
}
不要な条件評価を削除するのは最適化の基本であり、実際、大抵のコンパイラでは上記相当のコードが生成されます。
このような最適化は処理速度の向上にとって大変有難いのですが、上記の関数は、本当にこのような最適化をしても良いのでしょうか?
シングルスレッドモデルの場合は、特に問題はありません。問題は、この変数が他のスレッドや、ハードウェアによって書き換えられる可能性がある場合です。つまり、上記のループはその関数をコールしているスレッド自身ではなく、割り込み処理や他のスレッドからの操作されることによって変化することを期待していたという場合です。
この場合は、上記のような最適化がなされた場合、途中で他スレッド、あるいは割り込み処理が値を変更したとしても、最適化のためにループから抜けることがなくなってしまいます。コンパイラは、現在の関数コンテクスト以外からその変数を操作することはないという前提にたって最適化を行っているため、このようなことが起こります。

そこで、このような複数スレッドからアクセスされる変数に対する最適化を抑制するために、volatile修飾子を使用します。具体的には、次の例のように、変数宣言にvolatile指定を追加します。
extern volatile int event_flag
これで、event_flagに対する最適化は抑制され、先ほどのような意図しない最適化も防ぐことが出来ます。

処理手順の保存

条件分岐の最適化以外にも、処理手順の最適化によって意図しない動きになることもあります。次の例を見てください。
extern int* p_regster1;
extern int* p_regster2;

void set_regester2(int val)
{
  /*必ず次の手順でレジスタ設定する必要がある*/
  *p_register1 = 1;
  *p_register2 = 0;
  *p_register2 = val;
  *p_register1 = 0;
}
register2の値を設定する前に、regster1を一旦1にしており、register2設定後今度は0を設定しています。組み込みソフトウェアなどでは、HWの仕様上このような一見(Cコード上)無駄な手順が必要なことがあります。 これも一般的なコードとして見れば冗長なので、volatile指定無しの場合、最適化される可能性があります。例えば、次のようになります。
void set_regester2(int val)
{
  *p_register2 = val;
  *p_register1 = 0;
}
このような最適化は、単一のスレッドからしか呼ばれない関数でも起こりえます。回避策は、先ほどと同様、変数のvolatile指定をつけることです。
extern int* p_regster1;
extern int* p_regster2;

4434046683 このようにvolatile指定は、組み込み系やマルチスレッドプログラミングで重要な役割を果たしており、その動作を理解することは重要です。

近年のコンパイラの最適化能力はすばらしいので、アセンブラを強く意識したコードを書く必要はなくなってきました。しかし、最適化による影響を正しく理解していないと、上記のように予期せぬ不具合に見舞われることがあります。仮想関数、ガベッジコレクション等、言語レベルでの機能も高級化してきていますが、その根底にある動作をある程度把握していないと、最適なコードは書けません。

最適化に関しては、デバッグ時にデバッガが表示する変数の値に関する注意などもあるのですが、それはまた別の機会に。

【関連リンク】
「組み込み」ならではの基礎知識 (組み込みネット)
法大奥山研究室:C言語 volatile
特集:スレッドの落とし穴 (ITmedia)

【関連書籍】
実践マルチスレッドプログラミング Steve Kleiman
Pthreadsプログラミング ブラッド・ニコルス 他
CプログラミングFAQ―Cプログラミングのよく尋ねられる質問 Steve Summit 北野欽一
Cプログラミングの落とし穴 Andrew Koenig 中村明
C/C++による組み込みシステムプログラミング
組み込みLinux入門―開発環境/デバイスドライバ/ミドルウェア/他OSからの移行
ITRONプログラミング入門―組み込みOSのデファクト・スタンダード プログラミング詳細とサービス・コール徹底解説


このエントリーをはてなブックマークに追加

トラックバック


この記事にトラックバックする(FC2ブログユーザー)

linuxの勉強初日(ではないが)

linux勉強初めてちょうど一ヶ月を記念して、勉強してきたことをまとめようとblogを作ったんだけど・・・。ネタがねえ・・・。今日はrpmパッケージについて学んださ。でもね、これってコマンドの使い方とかだけだったら覚えておく必要ないかもね。明日はtkcvsをインス...

コメントの投稿

非公開コメント

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

訂正

サンプルコードに間違いをご指摘頂いたので、訂正しました。

[誤]
・・・・
回避策は、先ほどと同様、変数のvolatile指定をつけることです。

extern int* p_regster1;
extern int* p_regster2;

[正]
extern volatile int* p_regster1;
extern volatile int* p_regster2;

回避も何もかわってませんでした (^^;)。ご指摘ありがとうございました m(_ _)m

よくわかりすばらしいです

volatileの説明がわかりやすくとてもよかったです、私の持っているK&R本にvolatileがないような気がしていたが・・・と思っていたのも解消しました。
このURLを知人にもすすめようと思います。

承認待ちコメント

このコメントは管理者の承認待ちです

承認待ちコメント

このコメントは管理者の承認待ちです
人気エントリ
最近の記事
本のおすすめ

4274065979

4844337858

482228493X

4904807057

4873114799


最近のコメント
Links
プロフィール
  • Author:proger
  • 組み込み関係で仕事してます
ブログ内検索
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。