ルーグの備忘録

主にC#についてまとめてます。

仮想メソッドと脱仮想化

仮想メソッドとは

“virtual”が頭に付いた関数やインターフェイス内の関数のこと。継承先のクラスでoverrideすることで、名前は同じなのにクラスによって違う動きをするメソッドを作ることができる。このような性質を多態性という。

仮想メソッドの内部実装

仮想メソッドは仮想関数テーブル(virtual function table)という手法を使って実装されている。C++の知識が必要らしいが、一応知っておきたい気はする。でも今回はスルーで。

仮想メソッドの実行コスト

仮想メソッドの実行コストとなる要因は2つ。

  1. 仮想関数テーブルを引くための間接参照の増加
  2. インライン展開ができない

特に後者の方がパフォーマンスに大きく影響する。

インライン展開とは

インライン展開とは、関数として呼び出すよりも中身を展開してしまった方が確実に良いと判定できる関数に対して、呼び出し箇所に関数の中身を展開するコンパイラーの最適化処理のこと。
関数の呼び出しや戻り時のジャンプによって発生するコストを無くすことができる。

public class InlineSample : MonoBehaviour
{
    private void Start()
    {
        string name = Name(); // Name()はインライン展開で"Louge"に置き換えられる
    }

    public string Name() => "Louge";
}

脱仮想化(devirtualization)とは

脱仮想化とは、メソッド内でさかのぼれば具体的な型がわかる場合に、仮装メソッドを通常のメソッド呼び出しに置き換えるコンパイラーの最適化処理のこと。
仮想関数の場合は多態的な動きをするのでインライン展開ができないが、脱仮想化を挟むことでインライン展開ができる場合がある。

public interface IPerson
{
    string Name();
}

public class Louge : IPerson
{
    string IPerson.Name() => "Louge";
}

public class DevirtualizationSample : MonoBehaviour
{
   private void Start()
    {
        IPerson person = new Louge();
        person.Name(); // IPerson.Name()の仮想呼び出しではなく、Louge.Name()を直接呼び出す。
    }
}

しかし実際問題、脱仮想化が入るような状況はかなりレアなので、基本的に仮想メソッドは普通のメソッドよりもコストが大きくなってしまうっぽい。

Unityの場合

Unityで用いられているコンパイラーであるIL2CPPでは脱仮想化が行われてない。つまり、仮装メソッドは仮装メソッドとしてでしか呼ばれないので、普通のメソッドよりもコストが必ず大きくなる。
但し.NET6では脱仮想化は行われるので、将来Unityが.NET6をサポートすれば高速化されるかもしれない。