Visual C++ 2005 への移行と生成コードの速度低下
「Visual C++ 6.0/ .NET 2003 から Visual C++ 2005 に移行したら生成されたプログラムのパフォーマンスが悪化した」という話はまあありがちといえばありがちかもしれません.少なくともあって不思議ではない話です.
一般にこの手の議論が面白いのはプロファイリングを行ってどこがどのように regression したかある程度分かってからです.単にこのコードが速くなった/遅くなっただけという雑談は読むだけ時間の無駄なのですが,得てしてネット上で目につくのはそういった雑談の方が多いように思います.系をブラックボックスのまま扱って結果だけ見て一喜一憂というのは私の本業の方でも陥りやすい罠だったりするので,こうやって他人事のように言っている場合ではなかったりしますけど.
まあいずれにせよ自分で問題を突き止められる人は解決まで自分で行ってしまうため,問題の存在自体があまり表に出てこないのかもしれません.というような話を思い出したのが以下のインシデントです.
- Bug Details: RTM VS2005 Optimiser broken
- http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=b53f7db8-d955-4b66-9ada-28e33a3ae22a
また,Microsoft の方からの回答で示されている以下の MSDN Forums のスレッドも参考になります.
- VC2005 %50 Slower then VC2003
- http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=165078&SiteID=1&PageID=2
ちなみに MSDN Forums のこのスレッドでのオチは _mbscmp 内のロケールに関する処理が Visual C++ 2005 で変更されたために performance regression が起きていたというものでした.議論の流れは以下のようになります.
- グラフィックスアプリケーションということで最初の投稿の段階では Pentium 4 最適化や SSE 最適化について疑われていた.
- 次にプロファイルを取ってみるとどうも STL の map と (標準 C++ のではない) string クラスの組み合わせが遅そうという話に.
- CString を利用したテストコードでの議論.
途中にサンプルコードが出てきたので,手元の環境でちょっとだけ実験してみました.少しだけコードに手を入れたので,実験に用いたコードを以下に示します.
/* for Visual C++ .NET 2003 /O2 /G7 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /FD /EHsc /MT /GS /arch:SSE2 /W3 /c /Zi /TP /O2 /G7 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /EHsc /MT /GS /arch:SSE2 /W3 /c /Zi /TP for Visual C++ 2005 /O2 /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /FD /EHsc /MD /arch:SSE2 /W3 /c /Zi /TP /O2 /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /EHsc /MD /arch:SSE2 /W3 /c /Zi /TP */ #include <windows.h> #include <tchar.h> #include <map> using namespace std; //#define _ATL_CSTRING_NO_CRT #include <atlstr.h> using namespace ATL; class Object { public: CString m_csObject; Object() { } Object( const Object& rhs ) { m_csObject = rhs.m_csObject; } Object( CString::PCXSTR pcszName ) { m_csObject = pcszName; } #ifdef CSTRING_COPY_ON_WRITE Object( const CString& name ) { m_csObject = name; } #endif virtual ~Object() { } }; typedef map<CString,Object> Object_Map; // Global Map Object_Map g_Map; // Test Function CString::PCXSTR getObjectName( CString::PCXSTR pcszObject ) { Object_Map::iterator itr = g_Map.find( pcszObject ); if(g_Map.end() != itr) { return (itr->second).m_csObject; } return NULL; } int _tmain(int argc, _TCHAR* argv[]) { int i; int j; CString cs; double el; LARGE_INTEGER s1, s2, f; ::QueryPerformanceFrequency(&f); ::QueryPerformanceCounter(&s1); for(i=0;i<500000;i++) { cs.Format( _T("Object:%d"), i ); g_Map[cs] = Object( cs ); } ::QueryPerformanceCounter(&s2); el = (double)(s2.QuadPart - s1.QuadPart); el /= double(f.QuadPart); printf( "Add Time: %f ms\n", el*1000 ); // Now search ::QueryPerformanceCounter(&s1); for(j=0;j<10;j++) { for(i=0;i<500000;i++) { cs.Format( _T("::A%d"), i ); getObjectName( cs ); } } ::QueryPerformanceCounter(&s2); el = (double)(s2.QuadPart - s1.QuadPart); el /= double(f.QuadPart); printf( "Search Time: %f ms\n", el*1000 ); return 0; }
これを Windows XP Professional x64Edition, AMD Athlon 64 3500+ という環境で実行してみました.上のスレッドでは MBCS (Multi-Byte Character Set) の CString で実験していますが,Unicode 使用時の結果及び CRT を使わない CString 実装 (_ATL_CSTRING_NO_CRT) の結果もあわせて示しています.まずは "Add Time" の結果から.
Character | CString | VC++ .NET 2003 [msec] | VC++ 2005 [msec] |
---|---|---|---|
MBCS | CRT | 2732 | 3503 |
MBCS | API | 6549 | 6638 |
Unicode | CRT | 2133 | 1999 |
Unicode | API | 1703 | 1574 |
確かに MBCS + CRT では Visual C++ 2005 への移行で performance regression があるようです.個人的には MBCS + API の組み合わせと Unicode + API の組み合わせが対照的なのが興味深いです.
次に "Search Time" の結果.
Character | CString | VC++ .NET 2003 [msec] | VC++ 2005 [msec] |
---|---|---|---|
MBCS | CRT | 8579 | 12596 |
MBCS | API | 31898 | 32239 |
Unicode | CRT | 9719 | 9590 |
Unicode | API | 5755 | 6075 |
こちらも "Add Time" と同じ傾向が見て取れます.
最後におまけですが,CString のコピーコンストラクタが呼び出されるようにしたバージョンの "Add Time" の結果です.
Character | CString | VC++ .NET 2003 [msec] | VC++ 2005 [msec] |
---|---|---|---|
MBCS | CRT | 2548 | 3313 |
MBCS | API | 6386 | 6443 |
Unicode | CRT | 1988 | 1750 |
Unicode | API | 1550 | 1377 |
元々のソースコードは Object クラスのコンストラクタで文字列ポインタ経由で CString をコピーしていますが,CString は内部に参照カウンタつきのバッファを持っていて,単純なコピーはバッファの参照カウンタを増やすだけで済ませることができます.このため一旦文字列ポインタを経由させるのではなく,直接 CString 同士のコピーコンストラクタを呼び出すように出来た方が若干パフォーマンスが改善するかもね,という動機で測定してみました.いずれも若干のパフォーマンス向上が見られるようです.
暇があったら VTune での比較結果も載せてみたいと思います.