Desempenho de chamada virtual "direta" vs. chamada de interface em C #

Esta referência parece mostrar que chamar um método virtual diretamente na referência de objeto é mais rápido do que chamá-lo na referência à interface que esse objeto implement

Em outras palavras

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();    
}

Vindo do mundo C ++, eu esperava que essas duas chamadas fossem implementadas de forma idêntica (como uma simples pesquisa de tabela virtual) e tivessem o mesmo desempenho. Como o C # implementa chamadas virtuais e qual é esse trabalho "extra" que aparentemente é feito quando se chama uma interfac

--- EDIT ---

OK, respostas / comentários que cheguei até agora implicam que há uma referência dupla de ponteiro para chamada virtual por meio de interface versus apenas uma referência negativa para chamada virtual por meio de objet

Então, por favor, alguém poderia explicarporqu isso é necessário? Qual é a estrutura da tabela virtual em C #? É "simples" (como é típico para C ++) ou não? Quais foram as trocas feitas no design da linguagem C # que levaram a isso? Não estou dizendo que esse é um design "ruim", estou simplesmente curioso para saber por que era necessári

Em poucas palavras, eu gostaria deCompreend o que minha ferramenta faz sob o capô para que eu possa usá-la com mais eficiência. E agradeceria se não recebesse mais tipos de respostas "você não deveria saber disso" ou "usar outro idioma".

--- EDIT 2 ---

Para deixar claro, não estamos lidando com um compilador de otimização de JIT que remove o despacho dinâmico: modifiquei o benchmark mencionado na pergunta original para instanciar uma classe ou outra aleatoriamente em tempo de execução. Como a instanciação ocorre após a compilação e após o carregamento / JIT da montagem, não há como evitar o envio dinâmico nos dois casos:

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

    }

}
--- EDITAR 3 ---

Se alguém estiver interessado, aqui está como meu Visual C ++ 2010 apresenta uma instância de uma classe que herda várias outras classe

Código

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;
    }
};

Debugger:

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)   *

Vários ponteiros de tabela virtual são claramente visíveis esizeof(C) == 8 (na compilação de 32 bits

O..

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

.. imprime ...

0027F778
0027F77C

... indicando que os ponteiros para diferentes interfaces dentro do mesmo objeto realmente apontam para diferentes partes desse objeto (ou seja, eles contêm endereços físicos diferentes

questionAnswers(5)

yourAnswerToTheQuestion