VisualStudio一覧

デバッグビルドの速度差(C vs C++)

VisualStudioを使用していて、デバッグビルドとリリースビルドでの速度差が顕著なのは最適化の有無の点からも言うまでも無いですが、同じようなコードをC vs C++で書いたときにも何か差があるように感じてならないです。

どうしてこのような差が出てくるか調べてみたところ、1つはテンプレートの展開に関する部分が大きいのではないか、と感じました。
たとえば、STLはテンプレートの塊ですが、これを使用するとDebug/Releaseで速度差が大きいですし、よくDebugでは使用にも耐えない、と聞きます。

templateの展開はインラインに行われるため早い、とは聞きますが、インライン展開するかどうかはコンパイラが決めることなので、デバッグビルド時には展開されないことが多いです。__forceinline を付けてもデバッグビルドではアセンブリを見ると展開されていなかったりします。
デバッグしていてステップインをテンプレートの関数やメンバにしたときにも潜れるということは、展開されておらず単なる関数呼び出しになっている証拠です。同じような処理だからとローカルに書いてあったコードをテンプレートによる関数実装に切り替えた途端に速度の低下が起こると思われます。
実際に手元で型変換のためのテンプレートをCスタイルのキャストを使うように変更しただけで速度は向上しました…。あまり良くはないのですが、実装をテンプレートからマクロへ変更することも有効のように思います。

(手元の環境では20万回ほどの実行される箇所で、Cスタイルにしたことで70msほど速度向上しました。)

最後に、STLのデバッグで速度を求めるときには、_SECURE_SCL と _HAS_ITERATOR_DEBUGGING を設定してビルドするという手もあります。これらはテンプレートの展開有無ではなく、範囲チェックやイテレータの所有権チェック、同期オブジェクト使用してのアクセスチェックとかの有効/無効を切り替える仕組みとなっています。
これの注意点としては、使用するプログラム全体でこの定義を揃えたビルドを行う必要があります。間違えるとビルド失敗や妙なエラーに悩まされるんじゃないでしょうか。

※ 完全に無効化は困るので速度に関してだけちょっと恩恵を受けたい、という場合には _SECURE_SCL は1にしておくのが良さそうです。速度ペナルティは、_HAS_ITERATOR_DEBUGGING のほうが影響しているとのこと。


Releaseビルドでの挙動違い

C++の話になりますが、VisualStudioを使っていて、Releaseビルドしているのに挙動が変わる、ということに何度か出遭ったことがあります。
その挙動が変わるというのが、デバッガ接続している状態でReleaseビルド実行体を実行しているときには正常に動くのに、実行体を単体で実行させたときには不正アクセス等で異常終了する、という感じです。Releaseビルドではデバッグ版のようにメモリを初期値で塗りつぶしたりという余計な操作を行わないことは知られています。あくまでこれはビルドでの挙動なので、今回のようなデバッガの有無では挙動が変わりません。となると、今回のこの挙動の差はいったいどこから来ているのだろうかと、ちょっと考え込んでしまいました。

調べてみると、デバッガの有無で変わるポイントとして、使用するヒープの性質が異なることがわかりました。デバッガが接続されていると、Low-Fragmentation Heap,(通称LFHというもの)が有効にならない、とあります。
参考: http://support.microsoft.com/kb/929136

妙な翻訳ですが、デバッグ時でもLFHを使いたい場合には、_NO_DEBUG_HEAPを有効にすればよいようです。そもそもLFHが使えない原因が他のヒープに関するデバッグフラグが設定されることのようで・・・。

さて、このような訳でデバッガの有無で使用されるヒープの種類が異なるということがわかりました。このヒープの差がおそらくReleaseビルドのアプリケーションの動作の差異となって現れたのではないかと考えます。ただし、たまたま使用されるメモリの中身が何かしらの残骸値であったために不正終了を引き起こしたという単純なケースもあると思います。

結局のところは、メモリの未初期化が影響してこのような差異に繋がっていることが可能性として高いので、この場合には1度そのようなクラスメンバの未初期化が残っていないか確認してみた方が良さそうです。


VisualStudio2012を入れての不具合?

VisualStudio を過去のバージョンをいくつかインストールしている人は自分だけでは無いと思います。
今回VisualStudio 2012をインストールした後で、過去のVisualStudioがエラーを吐くような状態になってしまい、ちょっと困っていた&解決策を見つけたので、メモがてら書いてみようと思います。

その今回困ったという現象が、VisualStudio 2012をインストールした後でVisualStudio 2010でのビルドがことごとく失敗するという物です。エラーメッセージとしては次のような物が出てきます。
LINK : fatal error LNK1123: COFF への変換中に障害が発生しました: ファイルが無効であるか、または壊れています。

そのときの画面の状態は以下のような感じです。

この現象が出てしまっている人はそれなりに居るようで、ほとんどの場合は次のような対処で問題を回避しているようです。

  • 埋め込みマニフェストをオフにする。
    (プロジェクトのプロパティで、マニフェストツール/入出力カテゴリで、埋め込みマニフェスト を”いいえ”にする)
  • あるいは、インクリメンタルリンクをオフにする
  • さらには、.rcのリソースファイルを削除(可能ならば)

でもこの問題の起こる原因は、VisualStudio 2010 にあり、VisualStudio 2012 の問題ではないということでした。問題解決のためには、VisualStudio 2010 の ServicePack (SP1)をインストールすることが必要でした。ここでSP1をインストールして再度同じプロジェクトをビルドしてみたところ、問題は起こらないようです。

問題が起こっている人は、VS10sp1-KB983509.exe (VisualStudio 2010 SP1)をインストールしてみてください。ちなみにVS2012を入れた後にインストールしても問題は起こらないようですよ。

まとめると、VisualStudioは複数インストールしてもいいが、その際に過去のバージョンのServicePackは事前に当てておきましょう、ということになるでしょうか。


VisualStudio 2012とDirectX SDK

どうやらVisualStudio2012を使う状況においては、DirectXSDKにも注意が必要のようです。
VisualStudio2010を使う際にもDirectXSDKの諸注意がありましたが、2012においても同様に注意が必要のようです。

まず結論からいうと、うまく動作するDirectX SDK にはある範囲があるようです。
手元にインストールしてあったSDKを調べてみるとこんな感じです。

  • DirectX SDK 2008 Nov NG
  • DirectX SDK 2009 Aug OK
  • DirectX SDK 2010 June OK

以下、失敗したときの参考事例と言うことでメモっておきます。

よく言われているのがDXSDK_DIRのインクルードを設定する部分です。
プロジェクトのプロパティで”C/C++” / “追加のインクルードディレクトリ” に $(DXSDK_DIR)Include を追加すると下記のようにエラーが出ます(一部抜粋)。

これについては、プロジェクトのプロパティから、
“VC++ディレクトリ”/”インクルードディレクトリ”の末尾に、$(DXSDK_DIR)include を追加してあげることでエラーが消えます。

しかし、先ほど言ったようにこれだけでは不完全でした。
XAudio2.hをインクルードするソースコードだとこの対策をしても下記のようなエラーが環境によっては出てしまうことがあります。

これは、_WIN32_WINNT がどこかで定義されていることが原因です。
それがVisualStudio2012が標準で参照するWindowsSDKDirの中にあるXaudio2.hでエラーとなっています。このヘッダでは_WIN32_WINNTはWindows8の値がくることを想定しているようです。

よって、このままではWindows8以外を想定したビルドは通りません。
WindowsXP~7等で動作するアプリケーションのためにはDirectX SDKに含まれているXAudio2のヘッダを使う必要がありそうです。

このためには、DirectXのヘッダを読み込む優先順をあげる必要があります。
それは先ほどの$(DXSDK_DIR)Includeを設定した部分にあります。
優先順をあげて、VisualStudio2012の標準ヘッダ群より先にするために、”C/C++”/”追加のインクルードディレクトリ”のほうに設定します。ただし、2008Novのバージョンではエラーとなったことを上記の方で既に体験しています・・・・。

そこで、DirectX SDKのバージョンを上げてみます。2009Augではどうだろうか、と。
試してみたところ、うまくビルドが通りました。
インクルードの順序を確認してみましたが、DirectXSDKのほうのヘッダを参照していることも確認できました。

同様に2010Juneでも確認してみましたが、同様に成功しました。

まとめると、DirectXSDKを正しく使用するためには以下の手順となります。

  1. DirectX SDKは2009Aug以降を使用すること。できれば公式にVisualStudio2010対応してるとされていた2010Juneがよいと思います。
  2. DirectXSDKのインクルードパスの設定は、プロジェクトのプロパティのVC++ディレクトリに対して行わず、”C/C++”の”追加のインクルードディレクトリ”にて行うこと。

この2点が大切です。
これを守らないと変なインクルードの順序となり、予期せぬ不都合が起こるかもしれません。

チェック環境

  • Windows7 Pro SP1 (x64)
  • VisualStudio 2012 RTM
  • DirectX SDK  2008Nov, 2009Aug, 2010Jun

現在はもっと新しい記事があります。
VisualStudio 2012とDirectX SDK 再び


スレッド同期のエトセトラ 第3回

Visual C++ 2005以降のvolatileのMS拡張

さらにvolatileについて、Microsoft独自拡張がなされてる記載を見つけました。
VisualStudio使って、Windowsアプリケーションを作る上ではこのルールだけでやっていけるのかもしれません。

Visual C++ 2005 には、volatile 変数へのアクセスに関して、標準の C++ を補うために、マルチスレッドを想定したセマンティクスが定義されています。Visual C++ 2005 以降では、volatile 変数からの読み取りには Read-Acquire セマンティクスが、volatile 変数への書き込みには Write-Release セマンティクスが適用されるように定義されています。つまり、コンパイラによって、読み取りと書き込みが、互いを飛び越えて移動されることはありません。 さらに、Windows 上では、CPU による順序変更も確実に防ぐことができます。

これを見ると、2005以降のコンパイラで volatile付き変数を読み書きするタイミングで Read-AcquireやWrite-Releaseが適用されるみたいです。現在のvolatile変数だけでうまくいってる例も、実はこの拡張に依存してるのでは?と思った次第です。

あともう1つ。PowerPCのアーキテクチャでは x86/x64のときとは違って大胆にメモリの読み書き順序が異なって見えます。これに対してどのように対処すればいいのか不思議に思っていましたが、InterlockedXXXAcquire/Releaseの命令を通常版の代わりに使えば、きちんと各種メモリバリアを張ってくれるようです。

参考文献

Microsoft – Xbox 360 と Microsoft Windows でのロックレス プログラミングの考慮事項

volatile爆発しろ!