ダブル サンキング (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. このトピックでは、ダブル サンキングが発生して、パフォーマンスを向上させることを回避する方法について説明します。This topic discusses where double thunking occurs and how you can avoid it to improve performance.

RemarksRemarks

使用してコンパイルした場合、既定 /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

次の例では、ダブル サンキングを示します。The following sample demonstrates double thunking. ネイティブのコンパイル時に (せず /clr)、仮想関数への呼び出しmainに 1 回の呼び出しが生成されます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