「例外が発生しなければ例外コストなんて無いじゃん」
という僕の意見が勉強会で数名の方からエライ否定的に言われたので非常に気分が悪いw
C++のみだけど検証させていただきました。
尚、使用したコンパイラはVC9でコンパイルオプションは
cl /Ox /EHsc /Fa
です。
まず何かの処理をNULLチェックで書いてみます。
void null_check()
{
void* p = func1();
if(p) {
p = func2(p);
if(p) {
p = func3(p);
}
}
}
これを例外処理で書いたコードは以下のものです。
void with_try_catch()
{
try {
void* p = func1();
p = func2(p);
p = func3(p);
} catch(...) {
exception_sequence();
}
}
NULLチェックのコンパイル結果が以下の通り。
?null_check@@YAXXZ PROC ; null_check
; Line 19
call ?func1@@YAPAXXZ ; func1
; Line 20
test eax, eax
je SHORT $LN1@null_check
; Line 21
push eax
call ?func2@@YAPAXPAX@Z ; func2
add esp, 4
; Line 22
test eax, eax
je SHORT $LN1@null_check
; Line 23
push eax
call ?func3@@YAPAXPAX@Z ; func3
pop ecx
$LN1@null_check:
; Line 26
ret 0
無駄のないコードです。素晴らしい。
try~catchのコンパイル結果が以下の通り。但し例外が発生しなければ呼ばれることが無いcatch節の部分は載せていません。
?with_try_catch@@YAXXZ PROC ; with_try_catch
; File c:\cygwin\home\andochin\exptest.cpp
; Line 7
push ebp
mov ebp, esp
push -1
push __ehhandler$?with_try_catch@@YAXXZ
mov eax, DWORD PTR fs:0
push eax
push ecx
push ebx
push esi
push edi
mov eax, DWORD PTR ___security_cookie
xor eax, ebp
push eax
lea eax, DWORD PTR __$EHRec$[ebp+4]
mov DWORD PTR fs:0, eax
mov DWORD PTR __$EHRec$[ebp], esp
; Line 8
mov DWORD PTR __$EHRec$[ebp+12], 0
; Line 9
call ?func1@@YAPAXXZ ; func1
; Line 10
push eax
call ?func2@@YAPAXPAX@Z ; func2
add esp, 4
; Line 11
push eax
call ?func3@@YAPAXPAX@Z ; func3
add esp, 4
$LN7@with_try_c:
; Line 15
mov ecx, DWORD PTR __$EHRec$[ebp+4]
mov DWORD PTR fs:0, ecx
pop ecx
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret 0
例外を使用する場合の方がcatch節のアドレスと全レジスタ退避コードが出る分オーバーヘッドがありますね。赤字になっている部分が例外のために追加された処理です。
確かにオーバーヘッドがあるのですが、例外のために追加されている処理はスタックへの情報保存・退避で、例えば例外の為の初期化処理用のサブルーチンコールなどは行われていません。ということはアセンブラレベルでここで赤字になっている数十ステップのみ処理が増えていることになります。
# 個人的な感想で言うとこれが気になるのは頻繁に呼び出され且つ短い関数(メソッド)の場合だけじゃない?
この例外処理のための初期化・終了処理はtry節の中で行われる処理が変わっても同じものとなるでしょう。又、このような単純な関数ではなくローカル変数を多数使用する処理になった場合NULLチェックを行っているコードの方でもスタック上にローカルフレームを確保する処理が追加されるのでオーバーヘッドと呼べる部分は減ります。
# 更に変数宣言にregister修飾した場合esi/ediの退避コードが出る可能性もあるかも
それから僕が
「ifが無くなるんだから例外使った方が例外発生しない場合はむしろ早くなるんじゃない?」
と言った事もエラく否定された気がしますが、例外を使った場合のtry節の中の処理はNULLチェックコードが不要になる為むしろ早くなっています。
とは言え昨日のセッションはC#だったので、C#だとこういったコンパイル結果にはならないんでしょうね。C#覚えたら検証してみようかな。
例外処理のためにレジスタその他をスタックに積むなりしなきゃいけないって事に気が付いていなかったのは僕の知識不足故ですが(Cでsetjmp/longjmpの実装見てるんだから気付けよって感じ)、まんざら間違った事は言ってなかったと思うんだけどなぁ…自分の説明力不足を痛感したのでした。
# てかさ、何で誰も納得できるように説明してくれないの?こうなるよって言ってくれればそれで済む話なのに…
# 曖昧な言い方でスタックを余計に取るって言われたら普通ローカルフレームのebpを計算する時の値が変わるだけだって思うよ。