Doppeltes Thunking (C++)

Double thunking bezieht sich auf den Leistungsverlust, den Sie erleben können, wenn ein Funktionsaufruf in einem verwalteten Kontext eine verwaltete Visual C++-Funktion aufruft und wo die Programmausführung den systemeigenen Einstiegspunkt der Funktion aufruft, um die verwaltete Funktion aufzurufen. In diesem Thema wird erläutert, wo doppeltes Thunking auftritt und wie Sie es vermeiden können, um die Leistung zu verbessern.

Hinweise

Standardmäßig bewirkt die Definition einer verwalteten Funktion beim Kompilieren mit /clr, dass der Compiler einen verwalteten Einstiegspunkt und einen systemeigenen Einstiegspunkt generiert. Dadurch kann die verwaltete Funktion von systemeigenen und verwalteten Anrufwebsites aufgerufen werden. Wenn jedoch ein systemeigener Einstiegspunkt vorhanden ist, kann es sich um den Einstiegspunkt für alle Aufrufe der Funktion sein. Wenn eine aufrufende Funktion verwaltet wird, ruft der systemeigene Einstiegspunkt den verwalteten Einstiegspunkt auf. In der Tat sind zwei Aufrufe erforderlich, um die Funktion aufzurufen (daher doppeltes Thunking). Beispielsweise werden virtuelle Funktionen immer über einen systemeigenen Einstiegspunkt aufgerufen.

Eine Lösung besteht darin, dem Compiler mitzuteilen, dass kein systemeigener Einstiegspunkt für eine verwaltete Funktion generiert wird, dass die Funktion nur aus einem verwalteten Kontext aufgerufen wird, indem die __clrcall Aufrufkonvention verwendet wird.

Wenn Sie eine verwaltete Funktion exportieren (dllexport, dllimport), wird ein systemeigener Einstiegspunkt generiert, und jede Funktion, die diese Funktion importiert und aufruft, wird über den systemeigenen Einstiegspunkt aufgerufen. Um doppeltes Thunking in dieser Situation zu vermeiden, verwenden Sie keine systemeigene Export-/Importsemantik; verweisen Sie einfach über #using die Metadaten (siehe #using Richtlinie).

Der Compiler wurde aktualisiert, um unnötige Doppeltes Thunking zu reduzieren. Beispielsweise wird jede Funktion mit einem verwalteten Typ in der Signatur (einschließlich Rückgabetyp) implizit als __clrcallgekennzeichnet.

Beispiel: Doppeltes Thunking

Beschreibung

Im folgenden Beispiel wird das Doppelte Thunking veranschaulicht. Beim kompilierten systemeigenen Aufruf (ohne /clr) generiert der Aufruf der virtuellen Funktion main einen Aufruf des TKopierkonstruktors und einen Aufruf des Destruktors. Ein ähnliches Verhalten wird erreicht, wenn die virtuelle Funktion mit /clr und __clrcall. Wenn sie jedoch gerade mit "/clr" kompiliert wurde, generiert der Funktionsaufruf einen Aufruf des Kopierkonstruktors, aber es gibt einen weiteren Aufruf des Kopierkonstruktors aufgrund des nativen zu verwalteten Thunk.

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: Wirkung doppelter Thunking

Beschreibung

In der vorherigen Stichprobe wurde gezeigt, dass doppeltes Thunking vorhanden ist. In diesem Beispiel wird der Effekt gezeigt. Die for Schleife ruft die virtuelle Funktion auf, und das Programm meldet die Ausführungszeit. Die langsamste Zeit wird gemeldet, wenn das Programm mit /clr kompiliert wird. Die schnellsten Zeiten werden gemeldet, wenn sie ohne /clr kompiliert werden oder wenn die virtuelle Funktion mit __clrcalldeklariert 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

Siehe auch

Gemischte (native und verwaltete) Assemblys