Этот показатель , по-видимому, показывает, что вызов виртуального метода непосредственно на ссылке объекта является быстрее, чем вызывая его на ссылку на интерфейс этого объекта.
Другими словами:
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();
}
, исходя из мира C ++, я бы ожидал, что оба этих вызова будут реализованы одинаково (как простой вид виртуальной таблицы) и имеют одинаковую производительность. Как C # реализует виртуальные вызовы и в чем эта «дополнительная» работа, которая, по-видимому, сделана при вызове через интерфейс?
ОК, ответы / комментарии, которые я получил до сих пор подразумевает, что есть Двойной указатель-неработающий для виртуального вызова через интерфейс против всего лишь одного размышления для виртуального вызова через объект.
Так что мог угодно объяснить Почему это необходимо? Какова структура виртуальной таблицы в C #? Это «плоский» (как типично для C ++) или нет? Каковы были проектные компромиссы, которые были сделаны в языковой конструкции C #, которые приводят к этому? Я не говорю, что это «плохой» дизайн, я просто любопытно, почему это было необходимо.
в двух словах, я бы хотел понять , что мой инструмент делает под капотом, поэтому я могу использовать его более эффективно. И я был бы признателен, если бы у меня больше не получишь ", вы не должны знать, что« или «использовать другой язык» типов ответов.
Просто чтобы понять, что мы не имеем дело с некоторыми компиляторами jiT Optimization здесь, что удаляет динамическую динамику: я изменил тест, упомянутый в оригинальном вопросе, чтобы создать один класс или другой случайно во время выполнения. Поскольку мнение происходит после компиляции, а после сборки loading / jity, нет способа избежать динамической рассылки в обоих случаях:
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
}
}
Если кто-то заинтересован, вот как мой Visual C ++ 2010 год устанавливает экземпляр класса, который размножается - наследует другие классы:
код:
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;
}
};
Отладчик:
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) *
Несколько виртуальных указателей таблицы ясно видно, а SIZEOF (C) == 8
(в 32-битной сборке).
.. ...
C c;
std::cout << static_cast(&c) << std::endl;
std::cout << static_cast(&c) << std::endl;
.. Принты ...
0027F778
0027F77C
... Указание того, что указатели на разные интерфейсы в одном и том же объекте фактически указывают на разные части этого объекта (то есть они содержат разные физические адреса).