2009-04-20 4 views
1

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

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. 
    } 
} 
+0

Ваш «SealedClass» на самом деле не определен как запечатанный. Так что это ничем не отличается от вашего класса Child1. –

+0

C#? Это поможет узнать. –

ответ

5

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

О, и this blog post by Eric Gunnerson объясняет, почему C# всегда генерирует callvirt.

0

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

5

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

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

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

Компилятор может выбрать, чтобы заметить это, и выдать IL-сообщение вызова вместо callvirt. Однако оба компилятора C# и VB.Net не хотят выполнять эту оптимизацию. Оба будут излучать callvirt.

JIT также может производить такую ​​оптимизацию. Он также не хочет этого делать.

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

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