Double conversion de code (thunking) (C++)
Le double médiateur fait référence à la perte de performances qui peut se produire lorsqu'un appel de fonction dans un contexte managé appelle une fonction managée Visual C++ et que l'exécution du programme appelle le point d'entrée natif de la fonction pour appeler la fonction managée.Cette rubrique explique où le thunk de double se produit et comment l'éviter pour améliorer les performances.
Notes
Par défaut, lors de la compilation avec /clr (pas /clr:pure), la définition d'une fonction managée provoque la génération par le compilateur un point d'entrée managé et un point d'entrée natif.Cela permet d'appeler la fonction managée depuis des sites d'appel natifs et managés.Toutefois, lorsqu'un point d'entrée natif existe, il peut être le point d'entrée pour tous les appels à la fonction.Si un appel de la fonction est managé, le point d'entrée natif appellera le point d'entrée managé.En fait, deux appels sont requis pour appeler la fonction (par conséquent, le double médiateur).Par exemple, les fonctions virtuelles sont toujours appelées à travers un point d'entrée natif.
Une solution consiste à demander au compilateur de ne pas générer un point d'entrée natif pour une fonction managée, et que la fonction ne sera appelée à partir d'un contexte managé, à l'aide de la convention d'appel de __clrcall .
De même, si vous exportez (dllexport, dllimport) d'une fonction managée, un point d'entrée natif est généré et toute fonction qui importe et appelle cette fonction appellera via le point d'entrée natif.Pour éviter la double médiateur dans cette situation, n'utilisez pas la sémantique native d'exportation/importation ; référencez simplement les métadonnées via #using (consultez directive #using (C++)).
Le compilateur a été mis à jour pour réduire la double médiateur inutile.Par exemple, toute fonction avec un type managé dans la signature (y compris le type de retour) sera marquée implicitement comme __clrcall.Pour plus d'informations sur l'élimination du double médiateur, consultez https://msdn.microsoft.com/msdnmag/issues/05/01/COptimizations/default.aspx.
Exemple
Description
L'exemple suivant illustre un double médiateur.Si compilé en natif (sans /clr), l'appel à la fonction virtuelle dans main génère un appel au constructeur de copie des T et un appel au destructeur.Le comportement similaire survient lorsque la fonction virtuelle est déclarée avec /clr et __clrcall.Toutefois, lorsqu'il vient d'être compilé avec /clr, l'appel de fonction génère un appel au constructeur de copie mais il existe un autre appel au constructeur de copie en raison de la double médiateur entre natif et managé.
Le code
// 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");
}
Exemple de sortie
__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)
Exemple
Description
L'exemple précédent a montré l'existence d'un double médiateur.Cet exemple montre son effet.La boucle d' for appelle la fonction virtuelle et rapporte le temps d'exécution du programme.Le temps le plus long rapporté correspond à une compilation du programme avec /clr.Le temps le plus rapide rapporté correspond à une compilation sans /clr ou si la fonction virtuelle est déclarée avec __clrcall.
Le code
// 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");
}
Exemple de sortie
4.2 seconds
after calling struct S