元ネタ: unnonouno: 動的型情報で仮想関数呼び出しを速くできるか
Javaだと実行時の情報を使って更なる最適化ができるという話が途中にありますが、同じようなことはC++でもできます。元の記事のようにインライン展開したコードを手書きしてやる必要はありません。
というわけで、Visual C++ 2010のProfile-Guided Optimization (PGO)でいってみましょう。
元のコードを再掲します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Parent { public: virtual ~Parent() {} virtual void fun() = 0; }; class Child : public Parent { public: void fun() {} }; int main() { Parent* c = new Child(); for (int i = 0; i < 1000000000; ++i) c->fun(); } |
これをPGOのインストルメントモード(/LTCG:PGINSTRUMENT)でビルド、プロファイルを取るために何回か実行、そして、プロファイル結果を反映させての最適化ビルド(/LTCG:PGOPTIMIZE)を行いました。
その結果、通常のリリースビルド(/O2 /Oi, /LTCG)では2.37~2.38秒ほどだったところ、0.82~0.83秒になりました(Cygwin bashのtimeで計測)。
Visual Studioのデバッガでステップ実行して、逆アセンブルの結果を見てみます。PGO最適化後はこんなコードが生成されていました。
Parent* c = new Child();
00311012 push 4
00311014 call dword ptr [__imp_operator new (3120A4h)]
0031101A add esp,4
0031101D test eax,eax
0031101F je main+38h (311048h)
00311021 mov dword ptr [eax],offset Child::`vftable' (31210Ch)
00311027 mov esi,eax
{
for (int i = 0; i < 1000000000; ++i)
00311029 mov edi,3B9ACA00h
0031102E mov edi,edi
c->fun();
00311030 mov eax,dword ptr [esi]
00311032 mov eax,dword ptr [eax+4]
00311035 cmp eax,offset Child::fun (311000h)
0031103A jne __controlfp_s+6 (31195Ah)
{
for (int i = 0; i < 1000000000; ++i)
00311040 dec edi
00311041 jne main+20h (311030h)
}
(省略)
c->fun();
0031195A mov ecx,esi
0031195C call eax
0031195E jmp main+30h (311040h)
00311035のcmpでvtblの項目がChild::funであるか比較、違っていればjneで0031195Aへ飛んでc->fun()を実行します。Child::funだった場合、次の行(00311040)ではdecを行なっており、Child::funがインライン展開されて空になったものと思います。
というわけで、インストルメントビルド→プロファイル収集のための実行→最適化ビルドと手間は必要ですが、C++でも実行時の情報に基づく最適化は実現できるという話でした。
なお、ここではVisual C++ 2010を用いましたが、同様のプロファイルに基づく最適化はGCCにもあります。Profile feedback in GCCによれば、GCC 4.1からのようです。ほかには、Intel C++にもあるでしょう。
スポンサード リンク |