Поделиться через


Двойное преобразование (С++)

Двойное преобразование вызывает снижение производительности, которое можно ощутить, когда вызов функции в управляемом контексте вызывает управляемую функцию Visual C++, а также когда выполнение программы вызывает машинную точку входа функции чтобы вызвать управляемую функцию. В данном разделе описаны причины возникновения двойного преобразования и методы предотвращения снижения производительности.

Примечания

По умолчанию при компиляции с ключом /clr (не с ключом /clr:pure) определение управляемой функции заставляет компилятор создавать управляемую точку входа и машинную точку входа. Это позволяет сделать управляемую функцию вызываемой из машинных и управляемых вызовов. Однако если существует машинная точка входа, то она может являться точкой входа для всех вызовов функции. Если вызываемая функция является управляемой, то машинная точка входа будет впоследствии вызывать управляемую точку входа. В результате для вызова функции требуется два вызова (отсюда название "двойное преобразование"). Например, виртуальные функции всегда вызываются через машинную точку входа.

Одно решение заключается в сообщении компилятору не создавать машинную точка входа для управляемой функции, и вызывать функцию из управляемого контекста с помощью соглашения о вызовах __clrcall.

Если управляемая функция экспортируется (dllexport, dllimport), создается машинная точка входа, и любая функция, которая импортирует и вызывает данную функцию, будет вызываться через машинную точка входа. В данной ситуации, чтобы избежать двойного преобразования, нужно использовать не машинную семантику экспорта и импорта, а простую ссылку на метаданные с помощью директивы #using (см. #using Directive (C/C++)).

Компилятор был доработан на сокращение нежелательного двойного преобразования. Например, любая функция с управляемым типом в сигнатуре (включая возвращаемый тип) будет явно помечена как __clrcall. Дополнительные сведения о двойном преобразовании см. https://msdn.microsoft.com/msdnmag/issues/05/01/COptimizations/default.aspx.

Пример

Описание

Ниже представлен пример двойного преобразования. При компиляции в машинный код (без ключа /clr) вызов виртуальной функции в main создает один вызов копии конструктора T и один вызов деструктора. Похожее поведение наблюдается, когда виртуальная функция объявлена с /clr и __clrcall. Однако если при компиляции использовался только ключ /clr, вызов функции создает вызов копии конструктора, но в нем существует другой вызов копии конструктора, соответствующий переходу от машинного к управляемому преобразованию.

Код

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

Пример результатов

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

Пример

Описание

В предыдущем примере было показано создание двойного преобразования. В данном примере показан результат. Цикл for вызывает виртуальную функцию и программа сообщает время выполнения. Максимальное время соответствует компиляции программы с ключом /clr. Минимальное время соответствует компиляции программы без ключа /clr или при объявлении виртуальной функции с __clrcall.

Код

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

Пример результатов выполнения

4.2 seconds
after calling struct S

См. также

Основные понятия

Смешанные (собственные и управляемые) сборки