Doppio thunk (C++)

La doppia ottimizzazione si riferisce alla perdita di prestazioni che è possibile riscontrare quando una chiamata di funzione in un contesto gestito chiama una funzione gestita di Visual C++ e dove l'esecuzione del programma chiama il punto di ingresso nativo della funzione per chiamare la funzione gestita. In questo argomento viene illustrato dove si verifica il doppio failover e come evitarlo per migliorare le prestazioni.

Osservazioni:

Per impostazione predefinita, durante la compilazione con /clr, la definizione di una funzione gestita fa sì che il compilatore generi un punto di ingresso gestito e un punto di ingresso nativo. Ciò consente di chiamare la funzione gestita da siti di chiamata nativi e gestiti. Tuttavia, quando esiste un punto di ingresso nativo, può essere il punto di ingresso per tutte le chiamate alla funzione. Se viene gestita una funzione chiamante, il punto di ingresso nativo chiamerà il punto di ingresso gestito. In effetti, sono necessarie due chiamate per richiamare la funzione (di conseguenza, double thunking). Ad esempio, le funzioni virtuali vengono sempre chiamate tramite un punto di ingresso nativo.

Una risoluzione consiste nel indicare al compilatore di non generare un punto di ingresso nativo per una funzione gestita, che la funzione verrà chiamata solo da un contesto gestito, usando la convenzione di chiamata __clrcall .

Analogamente, se si esporta (dllexport, dllimport) una funzione gestita, viene generato un punto di ingresso nativo e qualsiasi funzione che importa e chiama tale funzione chiamerà tramite il punto di ingresso nativo. Per evitare la doppia emergenza in questa situazione, non usare la semantica di esportazione/importazione nativa; è sufficiente fare riferimento ai metadati tramite #using (vedere #using Direttiva).

Il compilatore è stato aggiornato in modo da ridurre il doppio numero non necessario. Ad esempio, qualsiasi funzione con un tipo gestito nella firma (incluso il tipo restituito) verrà contrassegnata in modo implicito come __clrcall.

Esempio: Double thunking

Descrizione

L'esempio seguente illustra la doppia illustrazione. In caso di compilazione nativa (senza /clr), la chiamata alla funzione virtuale in main genera una chiamata al Tcostruttore di copia di e una chiamata al distruttore. Si ottiene un comportamento simile quando la funzione virtuale viene dichiarata con /clr e __clrcall. Tuttavia, quando appena compilata con /clr, la chiamata di funzione genera una chiamata al costruttore di copia, ma esiste un'altra chiamata al costruttore di copia a causa del threadk nativo-gestito.

Codice

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

Output di esempio

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

Esempio: Effetto di double thunking

Descrizione

L'esempio precedente ha dimostrato l'esistenza di double thunking. Questo esempio mostra l'effetto. Il for ciclo chiama la funzione virtuale e il programma segnala il tempo di esecuzione. Il tempo più lento viene segnalato quando il programma viene compilato con /clr. I tempi più rapidi vengono segnalati durante la compilazione senza /clr o se la funzione virtuale viene dichiarata con __clrcall.

Codice

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

Output di esempio

4.2 seconds
after calling struct S

Vedi anche

Assembly misti (nativi e gestiti)