Compartir vía


Doble thunk (C++)

Doble código thunk hace referencia a la pérdida de rendimiento que se puede experimentar cuando una llamada de función en un contexto administrado llama a una función administrada de Visual C++ y la ejecución del programa llama al punto de entrada nativo de la función para llamar a la función administrada. En este tema se explica dónde se produce la doble aplicación de código thunk y cómo se puede evitar para mejorar el rendimiento.

Comentarios

De forma predeterminada, al usar /clr en la compilación, la definición de una función administrada hace que el compilador genere un punto de entrada administrado y un punto de entrada nativo. Esto permite llamar a la función administrada desde sitios de llamada nativos y administrados. Sin embargo, cuando hay un punto de entrada nativo, puede ser el punto de entrada de todas las llamadas a la función. Si se administra una función de llamada, el punto de entrada nativo llama al punto de entrada administrado. En efecto, se requieren dos llamadas para invocar a la función (por tanto, es una doble aplicación de código thunk). Por ejemplo, las funciones virtuales siempre se llaman a través de un punto de entrada nativo.

Una solución es indicarle al compilador que no genere un punto de entrada nativo para una función administrada, que solo se llamará a la función desde un contexto administrado usando la convención de llamada __clrcall.

Del mismo modo, si exporta (dllexport, dllimport) una función administrada, se genera un punto de entrada nativo y cualquier función que importa y llama a esa función la llama a través del punto de entrada nativo. Para evitar la doble aplicación de código thunk en esta situación, no utilice la semántica nativa de exportación o importación; simplemente haga referencia a los metadatos a través de #using. Consulte #using (directiva).

El compilador se ha actualizado para reducir la doble aplicación de código thunk innecesaria. Por ejemplo, cualquier función con un tipo administrado en la firma (incluido el tipo de valor devuelto) se marca implícitamente como __clrcall.

Ejemplo: doble aplicación de código thunk

Descripción

El ejemplo siguiente muestra la doble aplicación de código thunk. Cuando se realiza una compilación nativa (sin /clr), la llamada a la función virtual de main genera una llamada al constructor de copia de T y una llamada al destructor. Un comportamiento similar se consigue cuando la función virtual se declara con /clr y __clrcall. Sin embargo, cuando se acaba de ejecutar una compilación con /clr, la llamada de función genera una llamada al constructor de copia, pero hay otra llamada al constructor de copia debido a la conversión de nativo a administrado que realiza el código thunk.

Código

// 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");
}

Salida de ejemplo

__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)

Ejemplo: Efecto de la doble aplicación de código thunk

Descripción

El ejemplo anterior muestra la existencia de una doble aplicación de código thunk. En este ejemplo se muestra su efecto. El bucle for llama a la función virtual y el programa notifica el tiempo de ejecución. El tiempo más lento se notifica cuando el programa se compila con /clr. Los tiempos más rápidos se notifican cuando no se usa /clr en la compilación o cuando la función virtual se declara con __clrcall.

Código

// 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");
}

Salida de ejemplo

4.2 seconds
after calling struct S

Consulte también

Ensamblados mixtos (nativos y administrados)