Partager via


Double conversion de code (thunking) (C++)

Le double conversion de code fait référence à la perte de performances pouvant survenir lorsque l’appel d’une 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 la double conversion et comment l’éviter pour améliorer vos performances.

Remarques

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 managés. Toutefois, lorsqu’un point d’entrée natif existe, il peut s’agir du point d’entrée de tous les appels à la fonction. Si une fonction appelante est managée, le point d’entrée natif appelle alors le point d’entrée managé. Deux appels sont ainsi requis pour appeler la fonction (d’où la double conversion). Par exemple, les fonctions virtuelles sont toujours appelées via un point d’entrée natif.

Une solution consiste à indiquer au compilateur de ne pas générer de point d’entrée natif pour une fonction managée car elle 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 la double conversion 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 la double conversion superflue. Par exemple, toute fonction dont la signature dispose d’un type managé (y compris le type de retour) est implicitement marquée comme __clrcall.

Exemple : Double conversion

Descriptif

L’exemple suivant illustre la double conversion. En cas de compilation native (sans /clr), l’appel à la fonction virtuelle dans main génère un appel au constructeur de copie de T et un appel au destructeur. Une réaction similaire est obtenue 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 un autre appel au constructeur de copie existe également du fait de la conversion 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 de la double conversion

Descriptif

L’exemple précédent a démontré l’existence d’une double conversion. Cet exemple en montre l’effet. La boucle for appelle la fonction virtuelle et le programme signale la durée d’exécution. La durée le plus longue est signalée lorsque le programme est compilé avec /clr. Les durées les plus courtes sont signalées 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

Voir aussi

Assemblys mixtes (natifs et managés)