== null が激重(再検証)
前回の記事で、== nullが激重になる原因が例外が投げられていることにあると述べたが
ソースコードを見て推測しただけで、きっちりトレースして調べたわけではなかったので
本当にそうなのか詳しく調べてみた。
現象の確認
Borland C++ Builder 6 のデバッガでステップ実行してみたところ
やはり例外が投げられていた。
eTJSVariantErrorという種類の例外だ。
この例外はtTJSVariant::NormalCompareのtry-catch節ですぐに捕まえられて
何事もなかったかのようにひっそりと処理されてしまうため
コンソールやSystem.exceptionHandlerなどでは捕捉できない。
負荷の特定
例外が投げられているのは確かだが、
異なる型との == null が重い原因は本当に例外のせいなのだろうか?
負荷の原因を細かく特定するため、ソースコードをいじって速度を計測してみた。
(1) | 改変なし | 11554 ms |
(2) | AsReal で TJSThrowVariantConvertError(); を呼ばずに、 throw eTJSVariantError(L””); としたもの。 |
6448 ms |
(3) | AsReal で TJSThrowVariantConvertError(); を呼ばずに、 throw 0; としたもの。 |
6357 ms |
(4) | AsReal で TJSThrowVariantConvertError(); を呼ばずに、 return 0; としたもの。 |
74 ms |
(5) | AsReal を呼ぶ前に左辺と右辺のどちらかが tvtObject なら return false; としたもの。 |
47 ms |
結果から、次のことがわかる。
例外処理が負荷の半分を占めている
そしてもう半分を TJSThrowVariantConvertError 内の処理が占めている。
TJSThrowVariantConvertError 内で行われている処理は
エラーメッセージを作成する文字列処理である。
修正案
実験結果から例外の負荷が非常に大きいとわかったので、
これを回避する方法について考えて見よう。
例外を投げているのはAsRealなので、
AsRealが例外を投げないようにする案がすぐ思いつくが、それはかなりデンジャラスである。
AsRealは他の場所でも使われているし、何よりAsRealが例外を投げるのはおそらく仕様上の動作であり、
その改変による影響範囲は計り知れない。
ここは実験(5)でやっていたように、
AsRealを呼ぶ前にtvtObjectを別途処理するようにするのが最もシンプルで安全な解決策だろう。
そもそもAsRealが呼ばれる時点で、型はInt、Real、Object、Octetの組み合わせしかないので
エラー検出をAsRealにわざわざ頼る必要は、もはやあまりないように思う。
tvtObjectやtvtOctetを判定ではじいて別途処理した場合、
判定回数が増加するというデメリットがある。
しかしそのデメリットは判定の優先順位の見直しである程度軽減できるだろう。
ObjectやOctetと他の型の値を比較するケースより、
IntとRealを比較するケースの方が圧倒的に多いであろうからだ。
さらなる最適化
またこれはあくまでアイデアにすぎないが、tvtObjectのような型の種類を表す定数を
ビットフラグ的な値にすれば、判定をより効率化できるかもしれない。
if(vt==tvtString || val2.vt==tvtString){ }
if(vt==tvtVoid || val2.vt==tvtVoid){ }
if((vt==tvtInt || vt==tvtReal) && (val2.vt==tvtInt || val2.vt==tvtReal)){ }
if(vt==tvtObject || val2.vt==tvtOject){ }
if(vt==tvtOctet || val2.vt==tvtOctet){ }
のような判定は
tjs_unit32 IntRealOR = tvtInt | tvtReal;
tjs_uint32 vtOR = vt | val2.vt;
if(vtOR & tvtString){ }
if(vtOR & tvtVoid){ }
if(vtOR == IntRealOR){ } // vt != val2.vt なので == で済む
if(vtOR & tvtObject){ }
if(vtOR & tvtOctet){ }
に簡略できる。
まとめ
などなどいろいろ書いてみたけれど…
=== を使えばおおよそ問題を回避できるので、とりあえずそれで!