ダブル サンキング (C++)Double Thunking (C++)

ダブルサンキングとは、マネージコンテキストの関数呼び出しが Visual C++ マネージ関数を呼び出すときに発生するパフォーマンスの低下を指します。プログラムの実行では、マネージ関数を呼び出すために、関数のネイティブエントリポイントを呼び出します。Double thunking refers to the loss of performance you can experience when a function call in a managed context calls a Visual C++ managed function and where program execution calls the function's native entry point in order to call the managed function. このトピックでは、2つのサンキングが発生する場所と、パフォーマンスを向上させるためにそれを回避する方法について説明します。This topic discusses where double thunking occurs and how you can avoid it to improve performance.

解説Remarks

既定では、 /clrを指定してコンパイルすると、マネージ関数の定義によって、コンパイラはマネージエントリポイントとネイティブエントリポイントを生成します。By default, when compiling with /clr, the definition of a managed function causes the compiler to generate a managed entry point and a native entry point. これにより、マネージ呼び出しサイトからマネージ関数を呼び出すことができます。This allows the managed function to be called from native and managed call sites. ただし、ネイティブエントリポイントが存在する場合は、関数のすべての呼び出しのエントリポイントになることがあります。However, when a native entry point exists, it can be the entry point for all calls to the function. 呼び出し元の関数がマネージドの場合、ネイティブエントリポイントはマネージエントリポイントを呼び出します。If a calling function is managed, the native entry point will then call the managed entry point. 実際には、関数を呼び出すために2つの呼び出しが必要になります (したがって、ダブルサンキング)。In effect, two calls are required to invoke the function (hence, double thunking). たとえば、仮想関数は、常にネイティブのエントリポイントを通じて呼び出されます。For example, virtual functions are always called through a native entry point.

解決策の1つとして、マネージ関数のネイティブエントリポイントを生成しないようにコンパイラに指示します。この関数は、 __clrcallの呼び出し規約を使用してマネージコンテキストからのみ呼び出されます。One resolution is to tell the compiler not to generate a native entry point for a managed function, that the function will only be called from a managed context, by using the __clrcall calling convention.

同様に、マネージ関数をエクスポート (dllexport、dllimport) した場合、ネイティブエントリポイントが生成され、その関数をインポートして呼び出すすべての関数は、ネイティブエントリポイントを通じてを呼び出します。Similarly, if you export (dllexport, dllimport) a managed function, a native entry point is generated and any function that imports and calls that function will call through the native entry point. このような状況でダブルサンキングを回避するには、ネイティブのエクスポート/インポートセマンティクスを使用しないでください。を使用してメタデータを参照するだけ #using です (「 #using ディレクティブ」を参照してください)。To avoid double thunking in this situation, do not use native export/import semantics; simply reference the metadata via #using (see #using Directive).

不要なダブルサンキングを減らすために、コンパイラが更新されました。The compiler has been updated to reduce unnecessary double thunking. たとえば、シグネチャのマネージ型を持つ関数 (戻り値の型を含む) は、暗黙的にとしてマークされ __clrcall ます。For example, any function with a managed type in the signature (including return type) will implicitly be marked as __clrcall.

Example

説明Description

次の例は、2つのサンキングを示しています。The following sample demonstrates double thunking. ネイティブコンパイル ( /clrなし) では、の仮想関数を呼び出すと、 main T のコピーコンストラクターとデストラクターの呼び出しが1回生成されます。When compiled native (without /clr), the call to the virtual function in main generates one call to T's copy constructor and one call to the destructor. 同様の動作は、仮想関数が /clrおよびで宣言されている場合にも行われ __clrcall ます。Similar behavior is achieved when the virtual function is declared with /clr and __clrcall. ただし、 /clrを使用してコンパイルしただけでは、関数呼び出しによってコピーコンストラクターへの呼び出しが生成されますが、ネイティブからマネージドサンクによってコピーコンストラクターへの別の呼び出しが発生します。However, when just compiled with /clr, the function call generates a call to the copy constructor but there is another call to the copy constructor due to the native-to-managed 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");
}

サンプル出力Sample Output

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

Example

説明Description

前のサンプルでは、ダブルサンキングの存在を示していました。The previous sample demonstrated the existence of double thunking. このサンプルでは、その効果を示します。This sample shows its effect. この for ループは仮想関数を呼び出し、プログラムは実行時間を報告します。The for loop calls the virtual function and the program reports execution time. プログラムが /clrでコンパイルされると、最も時間がかかる時間が報告されます。The slowest time is reported when the program is compiled with /clr. /Clrを指定せずにコンパイルする場合、またはと共に仮想関数が宣言されている場合は、最速の時刻が報告され __clrcall ます。The fastest times are reported when compiling without /clr or if the virtual function is declared with __clrcall.

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

出力例Sample Output

4.2 seconds
after calling struct S

関連項目See also

混合 (ネイティブおよびマネージ) アセンブリMixed (Native and Managed) Assemblies