Double conversion de code (thunking) (C++)
Le double thunking fait référence à la perte de performances que vous pouvez rencontrer lorsqu’un appel de fonction dans un contexte managé appelle une fonction managée Visual C++ et où l’exécution du programme appelle le point d’entrée natif de la fonction afin d’appeler la fonction managée. Cette rubrique explique où se produit le double thunking et comment vous pouvez l’éviter pour améliorer les performances.
Notes
Par défaut, lors de la compilation avec /clr, la définition d’une fonction managée entraîne la génération d’un point d’entrée managé et d’un point d’entrée natif. Cela permet à la fonction managée d’être appelée à partir de sites d’appel natifs et gérés. Toutefois, lorsqu’un point d’entrée natif existe, il peut s’agir du point d’entrée pour tous les appels à la fonction. Si une fonction appelante est gérée, le point d’entrée natif appelle alors le point d’entrée managé. En effet, deux appels sont requis pour appeler la fonction (par conséquent, double thunking). Par exemple, les fonctions virtuelles sont toujours appelées via un point d’entrée natif.
Une résolution consiste à indiquer au compilateur de ne pas générer de point d’entrée natif pour une fonction managée, que la fonction ne sera appelée qu’à partir d’un contexte managé, à l’aide de la convention d’appel __clrcall.
De même, si vous exportez (dllexport, dllimport) 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 le double thunking dans cette situation, n’utilisez pas la sémantique d’exportation/importation native ; référencez simplement les métadonnées via #using
(voir #using Directive).
Le compilateur a été mis à jour pour réduire le double thunking inutile. Par exemple, toute fonction avec un type managé dans la signature (y compris le type de retour) est implicitement marquée comme __clrcall
.
Exemple : Double thunking
Description
L’exemple suivant illustre le double thunking. En cas de compilation native (sans /clr), l’appel à la fonction virtuelle génère main
un appel au T
constructeur de copie et à un appel au destructeur. Un comportement similaire est obtenu 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 du thunk natif à managé.
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 : Effet du double thunking
Description
L’exemple précédent a démontré l’existence d’un double thunking. Cet exemple montre son effet. La for
boucle appelle la fonction virtuelle et le programme signale l’heure d’exécution. Le temps le plus lent est signalé lorsque le programme est compilé avec /clr. Les temps les plus rapides sont signalés lors de la compilation sans /clr ou si la fonction virtuelle est déclarée avec __clrcall
.
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