双重 Thunk (C++)
更新:2007 年 11 月
所谓双形式转换 (Thunk),是指在托管上下文中的函数调用调用 Visual C++ 托管函数以及程序执行为了调用托管函数而调用本机入口点时,您体验到的性能下降。此主题讨论发生双形式转换 (Thunk) 发生的位置以及如何避免它以改进性能。
备注
默认情况下,当使用 /clr(而不是 /clr:pure)进行编译时,托管函数的定义导致编译器生成一个托管入口点和一个本机入口点。这允许从本机和托管调用站点调用托管函数。但是,当存在本机入口点时,它可以是函数的所有调用的入口点。如果调用函数是托管的,则本机入口点将调用托管入口点。实际上,需要两个调用来调用该函数(因此,称为双形式转换 (Thunk))。例如,虚函数始终通过本机入口点调用。
一种解决方法是:通知编译器不要为托管函数生成本机入口点,这样只能通过使用 __clrcall 调用约定从托管上下文调用此函数。
类似地,如果导出 (dllexport, dllimport) 托管函数,将生成本机入口点,并且导入和调用该函数的任何函数将通过本机入口点进行调用。在这种情况下,若要避免双形式转换 (Thunk),请不要使用本机导出/导入语义;而应通过 #using 直接引用元数据(请参见 The #using Directive)。
在 Visual C++ 2005 中,更新了编译器以减少不必要的双形式转换 (Thunk)。例如,签名中具有托管类型(包括返回类型)的任何函数将隐式标记为 __clrcall。有关双形式转换 (Thunk) 消除的更多信息,请参见 https://msdn.microsoft.com/msdnmag/issues/05/01/COptimizations/default.aspx。
示例
说明
下面的示例演示双形式转换 (Thunk)。当编译本机(不使用 /clr)时,对 main 中的虚函数的调用生成一个对 T 的复制构造函数的调用和一个对析构函数的调用。当使用 /clr 和 __clrcall 声明虚函数时会获得相似的行为。然而,当仅使用 /clr 编译时,该函数调用将生成对复制构造函数的调用,但是,由于本机到托管的形式转换 (Thunk),并没有另一个对复制构造函数的调用。
代码
// double_thunking.cpp
// compile with: /clr
#include <stdio.h>
struct T {
T() {
puts(__FUNCSIG__);
}
T(const T&) {
puts(__FUNCSIG__);
}
~T() {
puts(__FUNCSIG__);
}
T& operator=(const T&) {
puts(__FUNCSIG__);
return *this;
}
};
struct S {
virtual void /* __clrcall */ f(T t) {};
} s;
int main() {
S* pS = &s;
T t;
printf("calling struct S\n");
pS->f(t);
printf("after calling struct S\n");
}
示例输出
__thiscall T::T(void)
calling struct S
__thiscall T::T(const struct T &)
__thiscall T::T(const struct T &)
__thiscall T::~T(void)
__thiscall T::~T(void)
after calling struct S
__thiscall T::~T(void)
示例
说明
前面的示例演示了存在双形式转换 (Thunk) 的情况。本示例说明它的影响。for 循环调用虚函数,并且程序报告执行时间。当使用 /clr 编译该程序时,会报告最慢的时间。当不使用 /clr 编译或者使用 __clrcall 声明虚函数时,会报告最快的时间。
代码
// double_thunking_2.cpp
// compile with: /clr
#include <time.h>
#include <stdio.h>
#pragma unmanaged
struct T {
T() {}
T(const T&) {}
~T() {}
T& operator=(const T&) { return *this; }
};
struct S {
virtual void /* __clrcall */ f(T t) {};
} s;
int main() {
S* pS = &s;
T t;
clock_t start, finish;
double duration;
start = clock();
for ( int i = 0 ; i < 1000000 ; i++ )
pS->f(t);
finish = clock();
duration = (double)(finish - start) / (CLOCKS_PER_SEC);
printf( "%2.1f seconds\n", duration );
printf("after calling struct S\n");
}
示例输出
4.2 seconds
after calling struct S