Как компилятор оптимизирует виртуальные методы, реализованные закрытым классом - PullRequest
1 голос
/ 21 апреля 2009

Мне интересно, как оптимизируется следующий код. В частности, о виртуальных и прямых звонках. Я прокомментировал, как я думаю, что все оптимизировано, но это только догадки.

public abstract class Super
{
    public abstract void Foo();

    public void FooUser()
    {
        Foo();
    }
}

public class Child1 : Super
{
    public override void Foo()
    {
        //doSomething
    }
}

public class SealedChild : Super
{
    public override void Foo()
    {
        //doSomething
    }
}

class Program
{
    void main()
    {
        Child1 child1 = new Child1();
        child1.Foo(); //Virtual call?
        child1.FooUser(); //Direct call and then a virtual call. 

        SealedChild sealedChild = new SealedChild();
        sealedChild.Foo(); //Direct call?
        sealedChild.FooUser(); 
        /* Two options: either a direct call & then a virtual call
         * Or: direct call with a parameter that has a function pointer to Foo, and then a direct call to foo.
         */

        Super super = child1;
        super.Foo(); //Virtual call.
        super.FooUser(); //Virtual call then direct call.
    }
}

Ответы [ 3 ]

5 голосов
/ 21 апреля 2009

В случае, если у вас есть виртуальный метод в запечатанном классе, а типом ссылки на объект является запечатанный класс, виртуального вызова можно было бы избежать. Возьмите следующий пример. Нет никакой фактической причины, по которой GetName нужно вызывать виртуально, потому что мы знаем, что не может быть подкласса Parent и, следовательно, никакой дальнейшей виртуальной отправки.

sealed class Parent : Child  {
  public override string GetName() { return "foo"; }
}

public void Test() {
  var p = new Parent();
  var name = p.GetName();
}

Компилятор мог бы заметить это и вывести инструкцию call IL вместо callvirt. Однако и C #, и VB.Net Compiler решили не выполнять эту оптимизацию. Оба будут испускать callvirt.

JIT также свободен для такой оптимизации. Он также решает не делать этого.

Это не значит, однако, что вы не должны закрывать свои уроки. Классы должны быть запечатаны, если вы на самом деле не собираетесь кому-то наследовать от них. В противном случае вы открываете себя для сценариев, которые вы точно не оценили.

Кроме того, ничто не мешает компиляторам и JIT реализовать это позднее.

5 голосов
/ 21 апреля 2009

Компилятор вообще не выполняет никакой оптимизации. Он всегда генерирует инструкцию IL 'callvirt' (вызов виртуальный). Среда выполнения может теоретически удалить виртуальную часть вызова, но каждый тест, который я видел и пробовал, указывает, что это не так. Учитывая, что даже полностью статичные компиляторы C ++ не могут сделать это в тривиальных ситуациях, кажется маловероятным, что JIT сможет это осуществить. И даже если бы они могли заставить его работать, существует гораздо больше распространенных ошибок производительности, на которые они могут потратить свое время.

О, и это сообщение в блоге Эрика Ганнерсона объясняет, почему C # всегда генерирует callvirt.

0 голосов
/ 21 апреля 2009

Если он был запечатан (возможно, отредактировал его?), Компилятор или JIT могли бы выполнить не виртуальный вызов, когда объект, как известно, является SealedChild, сохраняя косвенное указание. Java делает это, C #, кажется, не делает этого; Я не знаю, что делает JIT.

...