Двойное преобразование (С++)
Двойное thunking ссылается на потерю производительности, которую можно испытать, когда вызов функции в управляемом контексте вызывает управляемую функцию Visual C++ и где выполнение программы вызывает собственную точку входа функции, чтобы вызвать управляемую функцию. В этом разделе описывается, где происходит двойная отсутствование и как можно избежать повышения производительности.
Замечания
По умолчанию при компиляции с помощью /clr определение управляемой функции приводит компилятору к созданию управляемой точки входа и собственной точки входа. Это позволяет вызывать управляемую функцию из собственных и управляемых сайтов вызовов. Однако при наличии собственной точки входа она может быть точкой входа для всех вызовов функции. Если вызывающая функция управляется, то собственная точка входа вызовет управляемую точку входа. В действительности для вызова функции требуются два вызова (следовательно, двойное thunking). Например, виртуальные функции всегда вызываются через собственную точку входа.
Одно решение заключается в том, чтобы компилятор не создавал собственную точку входа для управляемой функции, что функция будет вызываться только из управляемого контекста с помощью соглашения о вызове __clrcall .
Аналогичным образом, при экспорте (dllexport, dllimport) управляемой функции создается собственная точка входа и любая функция, которая импортирует и вызывает эту функцию через собственную точку входа. Чтобы избежать двойного переключения в этой ситуации, не используйте собственную семантику экспорта и импорта; просто сослаться на метаданные с помощью #using
(см . директиву #using).
Компилятор был обновлен, чтобы уменьшить ненужные двойные thunking. Например, любая функция с управляемым типом в сигнатуре (включая возвращаемый тип) неявно будет помечена как __clrcall
.
Пример: двойное thunking
Description
В следующем примере показано двойное тюнкирование. При компиляции собственного кода (без /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)
Пример: эффект двойного тонинга
Description
В предыдущем примере показано существование двойного тонинга. В этом примере показан его эффект. Цикл 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