switch ステートメントの仮想テーブルを置き換える (必ずしも新しいとは限りません) 方法を思いつきました。この方法では、追加のメモリを犠牲にして仮想関数をインライン化できます。
テーブルルックアップを使用する代わりに、一種のスイッチが使用されます
switch (objecttype)
{
case objectA: inlined virtual function call for foo from objectA; break;
case objectB: inlined virtual function call for foo from objectB; break;
case objectC: inlined virtual function call for foo from objectC; break;
default: vtable call;
}
そのため、ポインタ ルックアップを使用して呼び出しを行う代わりに、比較が行われます。コードのインライン化は、既知のクラスに対して行うことができます。
これをうまく機能させるには (関数呼び出し以上のものを避けるため)、オブジェクトはその型を保存する必要があります。型は連続している必要があります。
例えば:
class A
{
ushort objectType; // internal id, say for class A it is 1000
ushort objectInc; // internal. represents a sort of offset into the jump table
}
class B : A
{
ushort objectInc; // one more than A's objectInc, has the same objectType
}
etc...
次に、switchステートメントを効率的なジャンプテーブルにして、objectType
(正しいことを確認して)objectInc
コードサイズを比較し、(一連の比較ではなく)仮想関数のコードに直接ジャンプするようにします。
私が知る限り、このスキームの欠点は、より多くのメモリ (より大きなクラスとよりインライン化された関数) とより複雑なコンパイラですが、仮想関数は直接インライン化できるため (switch ステートメント全体がインライン化される可能性があります)、ラッピング呼び出しはありません。余分なオーバーヘッドは、いくつかの比較とジャンプ (つまり ) による数サイクルの追加だけO(1)
です。
そのようなスキームのパフォーマンスと、それが使用されない理由について有益なコメントを持っている人はいますか? 比較によるキャッシュの無効化の可能性を除けば、かなり効率的だと思いますが、基本クラスの場合、平均して、直接インラインメソッド呼び出しのわずか数サイクルで実行できると思います。
ところで、このテーブルは、各オブジェクトの派生関数に対するインライン化された仮想関数呼び出しのリストのように見えます。
以下があるとします。
class A
{
void foo();
}
class B : A
{
override void foo();
}
class C : A
{
override void foo();
}
A a = new C();
a.foo(); // but calls fooWrap
/// internal
void fooWrap(A a)
{
switch(a.Type)
{
case A: a.foo(); break; // A.foo() can be inlined here
case B: b.foo(); break; // B.foo() can be inlined here
case C: c.foo(); break; // C.foo() can be inlined here
default: // use vtable lookup, a's type is not known at compile time
}
}
(通常fooWrap
は vtable ルックアップになります)
fooWrap
直接インライン化することもできます。その場合、への呼び出しのコストはswitchfoo
ステートメントだけであり、効率的なジャンプ リストを使用してさらに最適化できます。