gpt4 book ai didi

c# - "direct"虚拟调用与 C# 中的接口(interface)调用的性能

转载 作者:太空狗 更新时间:2023-10-29 23:49:38 24 4
gpt4 key购买 nike

This benchmark似乎表明直接在对象引用上调用虚拟方法比在对该对象实现的接口(interface)的引用上调用它更快。

换句话说:

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# 如何实现虚拟调用以及通过接口(interface)调用时明显完成的“额外”工作是什么?

--- 编辑---

好吧,到目前为止,我得到的答案/评论表明,对于通过接口(interface)的虚拟调用,存在双指针解引用,而对于通过对象的虚拟调用,只有一个解引用。

所以请有人解释一下为什么这是必要的? C#中虚表的结构是怎样的?它是否“扁平化”(C++ 的典型特征)?在导致这种情况的 C# 语言设计中做出的设计权衡是什么?我并不是说这是一个“糟糕”的设计,我只是想知道为什么有必要这样做。

简而言之,我想了解我的工具在幕后做了什么,以便我可以更有效地使用它。如果我不再得到“你不应该知道”或“使用另一种语言”类型的答案,我将不胜感激。

--- 编辑 2 ---

为了清楚起见,我们在这里不处理某些删除动态分派(dispatch)的 JIT 优化编译器:我修改了原始问题中提到的基准测试,以在运行时随机实例化一个类或另一个类。由于实例化发生在编译之后和程序集加载/JITing 之后,因此在这两种情况下都无法避免动态调度:

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

}

}

--- 编辑 3 ---

如果有人感兴趣,下面是我的 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<IA*>(&c) << std::endl;
std::cout << static_cast<IB*>(&c) << std::endl;

..打印...

0027F778
0027F77C

...表示指向同一对象内不同接口(interface)的指针实际上指向该对象的不同部分(即它们包含不同的物理地址)。

最佳答案

我认为文章 Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects 将回答您的问题。特别是,请参阅 * Interface Vtable Map and Interface Map 部分-,以及以下关于虚拟调度的部分。

JIT 编译器可能会为您的简单案例解决问题并优化代码。但一般情况下不会。

IFoo f2 = GetAFoo();

GetAFoo 被定义为返回一个 IFoo,那么 JIT 编译器将无法优化调用。

关于c# - "direct"虚拟调用与 C# 中的接口(interface)调用的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42471076/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com