Freigeben über


Doppeltes Thunking (C++)

Double Thunking bezieht sich auf den Leistungsverlust, den Sie erleben können, wenn ein Funktionsaufruf in einem verwalteten Kontext eine Visual C++ verwaltete Funktion aufruft und die Programmausführung den nativen Einstiegspunkt der Funktion aufruft, um die verwaltete Funktion aufzurufen. Dieses Thema diskutiert, wo Thunking auftritt und wie Sie es vermeiden können, um die Leistung zu verbessern.

Hinweise

Standardmäßig führt die Definition einer verwalteten Funktion beim Kompilieren mit /clr dazu, dass der Compiler einen verwalteten Einstiegspunkt und einen nativen Einstiegspunkt generiert. Dies erlaubt es, die verwaltete Funktion von nativen und verwalteten Aufrufstandorten aus aufzurufen. Wenn jedoch ein nativer Einstiegspunkt existiert, kann er der Einstiegspunkt für alle Aufrufe der Funktion sein. Wenn eine aufrufende Funktion verwaltet ist, wird der native Einstiegspunkt den verwalteten Einstiegspunkt aufrufen. Im Effekt sind zwei Aufrufe erforderlich, um die Funktion aufzurufen (daher Thunking). Zum Beispiel werden virtuelle Funktionen immer über einen nativen Einstiegspunkt aufgerufen.

Eine Auflösung besteht darin, dem Compiler mitzuteilen, keinen nativen Einstiegspunkt für eine verwaltete Funktion zu generieren, da die Funktion nur aus einem verwalteten Kontext aufgerufen wird, indem die __clrcall Aufrufkonvention verwendet wird.

Ähnlich verhält es sich, wenn Sie eine verwaltete Funktion exportieren (dllexport, dllimport), wird ein nativer Einstiegspunkt generiert und jede Funktion, die diese Funktion importiert und aufruft, wird über den nativen Einstiegspunkt aufgerufen. Um doppeltes Thunking in dieser Situation zu vermeiden, verwenden Sie nicht die native Export/Import-Semantik; verweisen Sie einfach auf die Metadaten über „#using“ (siehe „#using Anweisung“).

Der Compiler wurde aktualisiert, um unnötiges doppeltes Thunking zu reduzieren. Zum Beispiel wird jede Funktion mit einem verwalteten Typ in der Signatur (einschließlich Rückgabetyp) implizit als „__clrcall“ markiert.

Beispiel: Double Thunking

Beschreibung

Die folgende Stichprobe demonstriert Double-Thunking. Wenn nativ kompiliert (ohne /clr), erzeugt der Aufruf der virtuellen Funktion in main einen Aufruf des Kopierkonstruktors von T und einen Aufruf des Destruktors. Ähnliches Verhalten wird erreicht, wenn die virtuelle Funktion mit /clr und __clrcall deklariert wird. Wenn jedoch nur mit /clr kompiliert wird, erzeugt der Funktionsaufruf einen Aufruf des Kopierkonstruktors, aber es gibt einen weiteren Aufruf des Kopierkonstruktors aufgrund des nativ-zu-verwaltet Thunks.

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

Beispielausgabe

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

Beispiel: Effekt von doppeltem Thunking

Beschreibung

Die vorherige Stichprobe zeigte die Existenz von Double Thunking. Diese Stichprobe zeigt ihren Effekt. Die „for“-Schleife ruft die virtuelle Funktion auf und das Programm berichtet die Ausführungszeit. Die langsamste time wird berichtet, wenn das Programm mit /CLR kompiliert wird. Die schnellsten Zeiten werden berichtet, wenn ohne /CLR kompiliert wird oder wenn die virtuelle Funktion mit __clrcall deklariert wird.

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

Beispielausgabe

4.2 seconds
after calling struct S

Weitere Informationen

Gemischte (native und verwaltete) Assemblys