La Performance du “direct” virtuel appel contre appel d'interface en C#

Cette référence semble montrer que l'appel d'une méthode virtuelle directement sur l'objet de référence est plus rapide que d'appeler sur la référence à l'interface de cet objet implémente.

En d'autres termes:

interface IFoo {
    void Bar();
}

class Foo : IFoo {
    public virtual void Bar() {}
}

void Benchmark() {
    Foo f = new Foo();
    IFoo f2 = f;
    f.Bar(); //This is faster.
    f2.Bar();    
}

Venant du C++ monde, je me serais attendu que deux de ces appels seraient mises en œuvre à l'identique (comme une simple table virtuelle de recherche) et ont le même rendement. Comment est-ce que C# mettre en œuvre les appels virtuels et quel est ce "plus" du travail qui, apparemment, se fait lors de l'appel par l'intermédiaire d'un interface?

--- EDIT ---

OK, réponses/commentaires je me suis tellement bien qu'il est un double pointeur de déréférencement pour les appels virtuel par le biais de l'interface à l'opposé d'un déréférencement pour les appels virtuel par l'objet.

Si quelqu'un pourrait s'il vous plaît expliquer pourquoi est-ce nécessaire? Quelle est la structure de la table virtuelle en C#? Est-il "à plat" (comme il est typique pour le C++) ou pas? Quelles ont été la conception des compromis qui ont été faits en langage C# design qui conduisent à cette situation? Je ne dis pas que c'est un "mauvais" le design, je suis simplement curieux de savoir pourquoi il a été nécessaire.

En un mot, je voudrais comprendre ce que mon outil n'sous le capot, donc je peux l'utiliser plus efficacement. Et j'apprécierais si je n'ai pas eu plus de "vous ne devriez pas savoir que" ou "utiliser une autre langue" types de réponses.

--- EDIT 2 ---

Juste pour rendre les choses claires, nous ne sommes pas à traiter avec un compilateur JIT d'optimisation qui supprime la répartition dynamique: j'ai modifié l'indice de référence mentionné dans la question d'origine d'instancier une classe ou de l'autre de manière aléatoire au moment de l'exécution. Depuis l'instanciation se passe après la compilation et l'assemblage de chargement/JITing, il n'y a aucun moyen d'éviter la distribution dynamique dans les deux cas:

interface IFoo {
    void Bar();
}

class Foo : IFoo {
    public virtual void Bar() {
    }
}

class Foo2 : Foo {
    public override void Bar() {
    }
}

class Program {

    static Foo GetFoo() {
        if ((new Random()).Next(2) % 2 == 0)
            return new Foo();
        return new Foo2();
    }

    static void Main(string[] args) {

        var f = GetFoo();
        IFoo f2 = f;

        Console.WriteLine(f.GetType());

        //JIT warm-up
        f.Bar();
        f2.Bar();

        int N = 10000000;
        Stopwatch sw = new Stopwatch();

        sw.Start();
        for (int i = 0; i < N; i++) {
            f.Bar();
        }
        sw.Stop();
        Console.WriteLine("Direct call: {0:F2}", sw.Elapsed.TotalMilliseconds);

        sw.Reset();
        sw.Start();
        for (int i = 0; i < N; i++) {
            f2.Bar();
        }
        sw.Stop();
        Console.WriteLine("Through interface: {0:F2}", sw.Elapsed.TotalMilliseconds);

        //Results:
        //Direct call: 24.19
        //Through interface: 40.18

    }

}

--- EDITION 3 ---

Si quelqu'un est intéressé, voici comment mon Visual C++ 2010 définit une instance d'une classe qui se multiplient-hérite d'autres classes:

Code:

class IA {
public:
    virtual void a() = 0;
};

class IB {
public:
    virtual void b() = 0;
};

class C : public IA, public IB {
public:
    virtual void a() override {
        std::cout << "a" << std::endl;
    }
    virtual void b() override {
        std::cout << "b" << std::endl;
    }
};

Débogueur:

c   {...}   C
    IA  {...}   IA
        __vfptr 0x00157754 const C::`vftable'{for `IA'} *
            [0] 0x00151163 C::a(void)   *
    IB  {...}   IB
        __vfptr 0x00157748 const C::`vftable'{for `IB'} *
            [0] 0x0015121c C::b(void)   *

Plusieurs virtuel tableau de pointeurs sont clairement visibles, et sizeof(C) == 8 (en 32 bits).

...

C c;
std::cout << static_cast<IA*>(&c) << std::endl;
std::cout << static_cast<IB*>(&c) << std::endl;

..estampes...

0027F778
0027F77C

...indiquant que des pointeurs vers des interfaces différentes au sein du même objet en fait montrer les différentes parties de l'objet (c'est à dire qu'ils contiennent différentes adresses physiques).

C++ n'est pas nécessairement la force virtuelle de recherche. Si le type dynamique peut être déterminé au moment de la compilation, la fonction peut être appelée directement.
Une interface d'appel de méthode exige une double déréférencement de pointeur. C# devrais peut-être pas être la langue de votre choix si vous comptez nanosecondes. Le C et le C++ sont des langages qui sont optimisés pour que.
le fait que j'ai posé la question ne signifie pas que je suis "comptage de nanosecondes" sur tout projet concret. Je ne peux pas juste être curieux?
Votre question n'est pas de s'exprimer que d'intérêt.
Je vais reformuler: Dans les cas où le rendement est le facteur le plus critique, d'autres technologies devenir plus approprié. Fondamentalement, je suggère d'utiliser le bon outil pour le travail, et qu'il n'existe pas une telle chose comme un outil qui peut faire tout le meilleur.

OriginalL'auteur Branko Dimitrijevic | 2011-08-29