C++ 仮想関数は、C スタイルの関数ポインターを呼び出すのと同じくらい速く、ポリモーフィックな基底クラスで呼び出されますか? 本当に違いはありますか?
関数ポインターを利用し、それらをポリモーフィズムの仮想関数に変更する、パフォーマンスを重視したコードをリファクタリングすることを検討しています。
ほとんどの C++ 実装はこれと同様に機能すると思います (そしておそらく、C にコンパイルされた最初の実装では、次のようなコードが生成されました)。
struct ClassVTABLE {
void (* virtuamethod1)(Class *this);
void (* virtuamethod2)(Class *this, int arg);
};
struct Class {
ClassVTABLE *vtable;
};
次に、インスタンスが与えられた場合Class x
、そのメソッドを呼び出すのvirtualmethod1
は のようなものですx.vtable->virtualmethod1(&x)
。したがって、1 つの余分な逆参照、1 つのインデックス付きルックアップvtable
、および 1 つの追加の引数 (= this
) がスタックにプッシュされ、レジスタに渡されます。
ただし、コンパイラはおそらく、関数内のインスタンスで繰り返されるメソッド呼び出しを最適化できます。インスタンスClass x
は構築後にクラスを変更できないため、コンパイラは全体x.vtable->virtualmethod1
を共通のサブ式と見なし、ループから移動できます。したがって、この場合、単一の関数内で繰り返される仮想メソッド呼び出しは、単純な関数ポインターを介して関数を呼び出すのと同じ速度になります。
多くの違いが見られる可能性は低いですが、これらすべてのことと同様にthis
、パフォーマンスの違いを引き起こす可能性があるのは、多くの場合、小さな詳細 (コンパイラが仮想関数にポインターを渡す必要があるなど) です。関数自体は「virtual
ボンネットの下」の関数ポインターであるため、コンパイラーが処理を完了すると、おそらく両方のケースでかなり似たコードが得られます。
仮想関数をうまく使っているように聞こえますが、もし誰かが反対して「パフォーマンスの違いがあるだろう」と言ったら、私は「それを証明してください」と言うでしょう。しかし、その議論を避けたい場合は、既存のコードのパフォーマンスを測定するベンチマーク (まだない場合) を作成し、それ (またはその一部) をリファクタリングして、結果を比較してください。理想的には、いくつかの異なるマシンでテストして、自分のマシンではうまく機能するが、他のタイプのマシン (異なる世代のプロセッサ、異なる製造元またはプロセッサなど) ではそれほどうまくいかない結果が得られないようにします。
仮想関数呼び出しには 2 つの逆参照が含まれ、そのうちの 1 つはインデックス付き、つまり のようなもの*(object->_vtable[3])()
です。
関数ポインターを介した呼び出しには、1 つの逆参照が含まれます。
メソッド呼び出しでは、隠し引数を として受け取る必要もありますthis
。
メソッド本体が実質的に空で、引数や戻り値がない場合を除き、違いに気付く可能性はほとんどありません。
関数ポインター呼び出しと仮想関数呼び出しの違いは、上記がボトルネックであることを既に測定していない限り、無視できます。
唯一の違いは次のとおりです。
これは、仮想関数が呼び出す関数のアドレスをルックアップする必要があるためですが、関数ポインターは既にそれを認識しています (それ自体に格納されているため)。
C++ を使用しているため、仮想メソッドを使用する必要があります。