Joh*_*ell 8 c# c++ polymorphism virtual
我似乎记得在某处读到C#中虚拟调用的成本并不像在C++中那么高.这是真的?如果是这样 - 为什么?
AC#virtual call必须检查"this"是否为空,而C++虚拟调用则不是.所以我一般都看不出为什么C#虚拟调用会更快.在特殊情况下,C#编译器(或JIT编译器)可能能够比C++编译器更好地内联虚拟调用,因为C#编译器可以访问更好的类型信息.调用方法指令有时可能在C++中较慢,因为C#JIT可能能够使用更快的指令,只处理小偏移,因为它更多地了解运行时内存布局和处理器模型,然后是C++编译器.
但是,我们最多只谈论一些处理器指令.在调制解调器超标量处理器上,"空检查"指令很可能与"调用方法"同时运行,因此不需要时间.
如果在循环中进行调用,则所有处理器指令也很可能已经是1级高速缓存.但是数据不太可能是缓存,这些天从主内存读取数据值的成本与从1级缓存运行100条指令的成本相同.因此,不幸的是,在实际应用中,虚拟呼叫的成本甚至可以在很少的地方进行测量.
C#代码使用更多指令的事实当然会减少可以容纳在缓存中的代码量,这种影响无法预测.
(如果C++类使用多个内在性,那么成本会更高,因为必须修补"this"指针.同样,C#中的接口会添加另一个重定向级别.)
原问题说:
我似乎记得在某处读到过,C# 中的虚拟调用的成本相对而言没有C++ 高。
注意重点。换句话说,这个问题可以改写为:
我似乎记得在 C# 中读到过,虚拟和非虚拟调用同样慢,而在 C++ 中,虚拟调用比非虚拟调用慢......
所以提问者并没有声称 C# 在任何情况下都比 C++ 快。
可能是无用的消遣,但这激发了我对带有 /clr:pure 的 C++ 的好奇心,不使用 C++/CLI 扩展。编译器生成的 IL 会被 JIT 转换为本机代码,尽管它是纯 C++。所以这里我们有一种方法可以看到标准 C++ 实现在与 C# 相同的平台上运行时会做什么。
使用非虚拟方法:
struct Plain
{
void Bar() { System::Console::WriteLine("hi"); }
};
Run Code Online (Sandbox Code Playgroud)
这段代码:
Plain *p = new Plain();
p->Bar();
Run Code Online (Sandbox Code Playgroud)
... 导致call操作码与特定的方法名称一起发出,向 Bar 传递一个隐式this参数。
call void <Module>::Plain.Bar(valuetype Plain*)
Run Code Online (Sandbox Code Playgroud)
与继承层次结构进行比较:
struct Base
{
virtual void Bar() = 0;
};
struct Derived : Base
{
void Bar() { System::Console::WriteLine("hi"); }
};
Run Code Online (Sandbox Code Playgroud)
现在,如果我们这样做:
Base *b = new Derived();
b->Bar();
Run Code Online (Sandbox Code Playgroud)
calli相反,它会发出操作码,它会跳转到一个计算出的地址——所以在调用之前有很多 IL。通过将其转回 C#,我们可以看到发生了什么:
**(*((int*) b))(b);
Run Code Online (Sandbox Code Playgroud)
换句话说,将 的地址强制b转换为指向 int 的指针(恰好与指针大小相同)并取该位置的值,即 vtable 的地址,然后取 vtable 中的第一项,这是要跳转到的地址,取消引用它并调用它,将隐式this参数传递给它。
我们可以调整虚拟示例以使用 C++/CLI 扩展:
ref struct Base
{
virtual void Bar() = 0;
};
ref struct Derived : Base
{
virtual void Bar() override { System::Console::WriteLine("hi"); }
};
Base ^b = gcnew Derived();
b->Bar();
Run Code Online (Sandbox Code Playgroud)
这将生成callvirt操作码,与在 C# 中完全一样:
callvirt instance void Base::Bar()
Run Code Online (Sandbox Code Playgroud)
因此,当针对 CLR 进行编译时,Microsoft 当前的 C++ 编译器在使用每种语言的标准功能时没有与 C# 相同的优化可能性;对于标准的 C++ 类层次结构,C++ 编译器生成包含用于遍历 vtable 的硬编码逻辑的代码,而对于 ref 类,它将它留给 JIT 来找出最佳实现。