Udostępnij przez


Podwójna konwersja bitowa adresów (C++)

Podwójne thunking odnosi się do utraty wydajności, które można doświadczyć, gdy wywołanie funkcji w kontekście zarządzanym wywołuje funkcję zarządzaną programu Visual C++ i gdzie wykonywanie programu wywołuje natywny punkt wejścia funkcji w celu wywołania funkcji zarządzanej. W tym temacie opisano, gdzie występuje podwójne thunking i jak można go uniknąć w celu zwiększenia wydajności.

Uwagi

Domyślnie podczas kompilowania z /clr definicja funkcji zarządzanej powoduje, że kompilator generuje zarządzany punkt wejścia i natywny punkt wejścia. Dzięki temu funkcja zarządzana może być wywoływana z natywnych i zarządzanych lokacji wywołań. Jeśli jednak istnieje natywny punkt wejścia, może to być punkt wejścia dla wszystkich wywołań funkcji. Jeśli funkcja wywołująca jest zarządzana, natywny punkt wejścia wywoła zarządzany punkt wejścia. W efekcie dwa wywołania są wymagane do wywołania funkcji (stąd podwójne thunking). Na przykład funkcje wirtualne są zawsze wywoływane za pośrednictwem natywnego punktu wejścia.

Jednym rozwiązaniem jest powiedzenie kompilatorowi, aby nie wygenerował natywnego punktu wejścia dla funkcji zarządzanej, że funkcja będzie wywoływana tylko z kontekstu zarządzanego przy użyciu konwencji wywoływania __clrcall .

Podobnie, jeśli eksportujesz funkcję zarządzaną (dllexport, dllimport), generowany jest natywny punkt wejścia i każda funkcja importowana i wywołuje tę funkcję za pośrednictwem natywnego punktu wejścia. Aby uniknąć podwójnego thunking w tej sytuacji, nie należy używać natywnej semantyki eksportu/importu; po prostu odwołaj się do metadanych za pośrednictwem #using (patrz dyrektywa #using).

Kompilator został zaktualizowany w celu zmniejszenia niepotrzebnego podwójnego thunkingu. Na przykład każda funkcja z typem zarządzanym w podpisie (łącznie z typem zwrotnym) będzie niejawnie oznaczona jako __clrcall.

Przykład: Podwójne thunking

opis

W poniższym przykładzie pokazano podwójne thunking. Podczas kompilowania natywnego (bez /clr) wywołanie funkcji wirtualnej w main module generuje jedno wywołanie Tkonstruktora kopiowania i jedno wywołanie destruktora. Podobne zachowanie jest osiągane, gdy funkcja wirtualna jest zadeklarowana za pomocą /clr i __clrcall. Jednak po skompilowaniu z /clr wywołanie funkcji generuje wywołanie konstruktora kopiowania, ale istnieje inne wywołanie konstruktora kopiowania ze względu na natywnie zarządzane thunk.

Kod

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

Przykładowe dane wyjściowe

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

Przykład: Efekt podwójnego thunkingu

opis

W poprzednim przykładzie pokazano istnienie podwójnego thunkingu. Ten przykład pokazuje jego efekt. Pętla for wywołuje funkcję wirtualną, a program zgłasza czas wykonywania. Najwolniejszy czas jest zgłaszany, gdy program jest kompilowany z /clr. Najszybsze czasy są zgłaszane podczas kompilowania bez /clr lub jeśli funkcja wirtualna jest zadeklarowana za pomocą __clrcallpolecenia .

Kod

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

Przykładowe dane wyjściowe

4.2 seconds
after calling struct S

Zobacz też

Zestawy mieszane (natywne i zarządzane)