実行CPUの固定

テーマ:

一昨年だったか掲示板に書いた気がするのだが、このブログでは雑に扱っていたので少し解説する。


小宮日記(06/04/08) >別にマルチスレッド化も、並列化もしてないけど、CPU0とCPU1両方で動いてますね。謎だ。


現在の一般的なアプリはマルチプロセッサ上で動くことを考慮したプログラムになっていない。そのようなプログラムをマルチプロセッサ上(且つそれに対応したOSの支配下)で動かすと、単に空いているCPUで実行するようにOSが自動的に、しかも相当エエカゲンに割り当ててくる。これは多くのプロセス/スレッドが既に走っていて、OSから見たアプリはスケジューリングされたプロセス/スレッドの一つとして扱っているだけ、ということである。例えば、同一実行優先度を持つ3つのスレッドが走っていて、それを2つのプロセッサで均等に時分割実行させることを考えてみると、各スレッドは下図のように2つのプロセッサ間をひっきりなしに移動しているように見える。

これはプログラムから見てL1/L2キャッシュのヒット率を悪化させ、性能が低下する原因にもなる。そのような状況を回避したい場合は、SetThreadAffinityMask関数を用いる[*1]。詳しくはMSDNを参照のこと。この対処法は、例の最適化ガイド (p.96-98)にccNUMAの件も含めて書いてある[*2]


[*1] また、スレッドの実行優先度を制御したい場合は、SetThreadPriority関数を用いる。

[*2] 以前紹介した時はRev.3.05(Oct.2004)だったと思う。最新版はRev.3.06(Aug.2005)となっている。

AD

Cコードで永久ループを作りたい時、do { ... } while(1) とか while(1) { ... } といった書き方を目にすることがある。たまたま今日、そういうコードを見た。それらは文法として間違ってないし、意図通りに動くか否かどっちなんだ、と訊かれれば、間違いなく「動く」のだが、人間に厳しいコンパイラなら警告が出る。

さて、C言語の文法書(K&R)をよくよく読めば、一応「正統な永久ループの書き方」があることが判る。15年ほど前、あるプログラマが書いたコードを見ていて、そのことに気付いた。具体的には次の様なコードだ。


--
#define     ever    (;;)

void    main( void ) {
    for ever {
        process();
    }
}
--

この書き方は処理系依存ではなく、あらゆる(ANSI)Cコンパイラ(プリプロセッサ含む)が正しく記述されたCソースとして扱ってくれる。文法として正しい以上、そうでなければCコンパイラと言えなくなってしまうからだ。ひとひねり入っているが、このようなコーティングには、何か心に感じるものがある。

Cは高水準言語とは少し言い難い。しかしながら、こういうコードに出会うと、アルゴリズム表現の潜在的可能性を垣間見た気がする。結局、書き手次第で「言語の水準(レベル)が決まる」ということだ。

AD

注意すべきWarning

テーマ:

VisualC/C++が吐き出すコンパイル時の warningメッセージで、特に注意すべきメッセージについて挙げる。

CodeMeaning
C4172 ローカル変数またはテンポラリのアドレスを返します
auto変数ではなくstatic変数にしないと問題があるコーディング。これは概ね危険。油断しているとたまにやってしまうので注意。
C4706 条件式の比較値は、代入の結果になっています
ケアレスミスで "==" と書くべきところを単に "=" と書いてしまっている場合がある。定数と変数の比較は、定数を左項に書くようにすることで(単なるワーニングではなく)フェイタルエラー となるのですぐに気付く。しかしながら、変数同士の場合はワーニングでしかないので注意が必要。
C4127 条件式が定数です
if( 0 ) といった文を書くと起きる。稀に意図してそう書く場合があるが、このメッセージが出たら、一応はソースを確認しておいた方が無難。
C4715 '関数名' : 値を返さないコントロールパスがあります
関数の定義では戻値があるのに、単に return していると起きる。これも概ね危険。
C4700値が割り当てられていないローカルな変数 'xxx' に対して参照が行われました
auto変数を初期化せずに(あるいは代入せずに)使用した場合に、このメッセージが出る。アルゴリズムによってはそれでも問題は起きないが、やはりソースを確認しておいた方が無難。また、Debugモードでは正常に動作するが、Releaseモードだとうまく動かないような時、このワーニングが起きていることがよくある。
C4701 値が割り当てられていないローカルな変数 'xxx' に対する参照が行われる可能性があります
これは、C4700と同等。違いは、関数内部で参照が行われている時はC4700で、関数外部で参照される可能性がある時はC4701となる。例えば、初期化していないauto変数を戻り値にすると、このメッセージ(C4701)が出力される。
C4211非標準の拡張機能が使用されています : extern が static に再定義されました
グローバル宣言した変数をautoではないローカルなスタティク変数にしてしまった時に、このメッセージが出る。グローバル変数として使った変数名を失念してしまうとこのようなことになる。
C4554演算子の優先順位に問題があります。カッコを使用して優先順位を明確にしてください
C言語の仕様上の演算子の優先順順位を100%覚えていて常に間違いなく式を記述できる、などということは不可能だと考えた方がよい。一目見て優先順位が判らない場合、論理演算子と等号・不等号、三項演算子を含む式の場合は、括弧記号を用いて記述した方が後々問題が起きにくくなる。あるいは、複数行の式にした方がよく、見かけは冗長になるが最適化が効けば遅くなることはない。
C4033

(C4035)

'関数名' : 関数には戻り値が必要です
例えば、int func(...); と宣言している関数であるにも関わらず、int 型の戻値を返さないような関数だと、このようなワーニングが出る。「ついウッカリ忘れる」ということもあるが、関数の仕様を戻値なしから戻値ありに変更した場合、全ての return 部分についてチェックする必要がある。このワーニングを残したまま実行すると戻値は不定なので注意。
'関数名' : 戻り値がありません
C4035も同様のワーニング。return文そのものがない時にはC4035が出る。

これらの warningは、見逃すことができず、無視すると酷い目に遭うものばかりである。むしろ、fatal扱いである方が望ましい程の問題を引き起こすことが多い。例えば C4700は、「Debug構成でビルドすると巧く動くが、Release構成では不具合が起きる」といった現象がよくある。
なお、ワーニングが煩い時は、コンパイルエラーレベルを落とすか、#pragma warning(disable : 9999 ...) とプログラムの冒頭で指示することで強制的に消すことができるが、なるべくそのようなエラーメッセージ抑制は行わない方が無難かもしれない。

AD