Partilhar via


Duplo Thunking (C++)

Double thunking refere-se à perda de desempenho que se pode sentir quando uma chamada de função num contexto gerido invoca uma função gerida do Visual C++ e a execução do programa ativa o ponto de entrada nativo da função, a fim de chamar a função gerida. Este tópico aborda onde ocorre o double thunking e como pode ser evitado para melhorar o desempenho.

Observações

Por padrão, ao compilar com /clr, a definição de uma função gerenciada faz com que o compilador gere um ponto de entrada gerenciado e um ponto de entrada nativo. Isso permite que a função gerida seja chamada a partir de locais de chamada nativos e geridos. No entanto, quando existe um ponto de entrada nativo, ele pode ser o ponto de entrada para todas as chamadas para a função. Se uma função de chamada for gerenciada, o ponto de entrada nativo chamará o ponto de entrada gerenciado. Na verdade, duas chamadas são necessárias para invocar a função (portanto, duplo thunking). Por exemplo, as funções virtuais são sempre chamadas através de um ponto de entrada nativo.

Uma resolução é dizer ao compilador para não gerar um ponto de entrada nativo para uma função gerenciada, que a função só será chamada a partir de um contexto gerenciado, usando a convenção de chamada __clrcall .

Da mesma forma, se você exportar (dllexport, dllimport) uma função gerenciada, um ponto de entrada nativo será gerado e qualquer função que importar e chamar essa função chamará através do ponto de entrada nativo. Para evitar o "double thunking" nesta situação, não utilize a semântica nativa de exportação/importação; basta referenciar os metadados através de #using (ver Diretiva #using).

O compilador foi atualizado para reduzir o duplo thunking desnecessário. Por exemplo, qualquer função com um tipo gerenciado na assinatura (incluindo o tipo de retorno) será implicitamente marcada como __clrcall.

Exemplo: Double thunking (processo de duplo atraso)

Descrição

O exemplo a seguir demonstra o processo de double thunking. Quando compilada como nativo (sem /clr), a chamada para a função virtual em main gera uma chamada para o construtor de cópia de T e uma chamada para o destrutor. Comportamento semelhante é alcançado quando a função virtual é declarada com /clr e __clrcall. No entanto, quando compilada apenas com /clr, a chamada de função gera uma chamada para o construtor de cópia, mas há outra chamada para o construtor de cópia devido ao thunk nativo-gerenciado.

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

Saída de amostra

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

Exemplo: Efeito de duplo debulhar

Descrição

A amostra anterior demonstrou a existência de duplo thunking. Este exemplo mostra o seu efeito. O for loop chama a função virtual e o programa relata o tempo de execução. O tempo mais lento é relatado quando o programa é compilado com /clr. Os tempos mais rápidos são relatados ao compilar sem /clr ou se a função virtual for declarada com __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");
}

Saída de amostra

4.2 seconds
after calling struct S

Ver também

Montagens mistas (nativas e gerenciadas)