【发布时间】:2018-08-12 08:47:29
【问题描述】:
无论使用何种互操作技术,每次托管函数调用非托管函数时都需要特殊的转换序列(称为 thunk),反之亦然。这些 thunk 由 Visual C++ 编译器自动插入,但重要的是要记住,这些转换累积起来会在性能方面付出高昂的代价。
当然,CLR 始终调用 C++ 和 Win32 函数。为了处理文件/网络/窗口和几乎任何其他东西,必须调用非托管代码。它是如何摆脱分块惩罚的?
这是一个用 C++/CLI 编写的实验,可能有助于描述我的问题:
#define REPS 10000000
#pragma unmanaged
void go1() {
for (int i = 0; i < REPS; i++)
pow(i, 3);
}
#pragma managed
void go2() {
for (int i = 0; i < REPS; i++)
pow(i, 3);
}
void go3() {
for (int i = 0; i < REPS; i++)
Math::Pow(i, 3);
}
public ref class C1 {
public:
static void Go() {
auto sw = Stopwatch::StartNew();
go1();
Console::WriteLine(sw->ElapsedMilliseconds);
sw->Restart();
go2();
Console::WriteLine(sw->ElapsedMilliseconds);
sw->Restart();
go3();
Console::WriteLine(sw->ElapsedMilliseconds);
}
};
//Go is called from a C# app
结果是(一致的):
405 (go1 - pure C++)
818 (go2 - managed code calling C++)
289 (go3 - pure managed)
为什么 go3 比 go1 快有点神秘,但这不是我的问题。我的问题是,我们从 go1 和 go2 中看到,thunking 惩罚增加了 400 毫秒。 go3如何摆脱这个惩罚,since it calls C++进行实际计算?
即使这个实验由于某种原因无效,我的问题仍然存在 - CLR 真的每次调用 C++/Win32 时都会受到重击惩罚吗?
【问题讨论】:
-
实验确实无效。所有时间测量都应该在 Release 配置中完成,Release 中的函数 go1、go2 和 go3 可能会被删除,因为它们没有副作用。
-
@AlexF 你说得对,我是在发布模式下完成的,并且汇总并输出总数,因此不会被优化掉,结果是
269,306,330。所以我猜 Math.Pow 毕竟确实受到了分块的影响。请写下这个作为答案。 -
托管代码的全部目的是防止蓝屏。因此,为了确保您不会出现蓝屏,每个函数调用都必须受到异常处理程序的保护,或者必须彻底验证代码不会发生异常。因此,thunk 是执行堆栈中的包装器,它添加异常处理程序以捕获微处理器异常。