이중 썽킹(C++)
이중 unking은 관리되는 컨텍스트에서 함수 호출이 Visual C++ 관리 함수를 호출하고 프로그램 실행에서 관리되는 함수를 호출하기 위해 함수의 네이티브 진입점을 호출할 때 발생할 수 있는 성능 손실을 나타냅니다. 이 항목에서는 이중 펑킹이 발생하는 위치와 이를 방지하여 성능을 향상시키는 방법에 대해 설명합니다.
설명
기본적으로 /clr로 컴파일할 때 관리되는 함수의 정의로 인해 컴파일러가 관리되는 진입점 및 네이티브 진입점을 생성합니다. 이렇게 하면 네이티브 및 관리되는 호출 사이트에서 관리되는 함수를 호출할 수 있습니다. 그러나 네이티브 진입점이 있는 경우 함수에 대한 모든 호출의 진입점이 될 수 있습니다. 호출 함수가 관리되는 경우 네이티브 진입점은 관리되는 진입점을 호출합니다. 실제로 함수를 호출하려면 두 번의 호출이 필요합니다(따라서 이중 unking). 예를 들어 가상 함수는 항상 네이티브 진입점을 통해 호출됩니다.
한 가지 해결 방법은 컴파일러에 관리되는 함수에 대한 네이티브 진입점을 생성하지 않도록 지시하는 것입니다. 이 함수는 __clrcall 호출 규칙을 사용하여 관리되는 컨텍스트에서만 호출됩니다.
마찬가지로 관리되는 함수를 내보내는 경우(dllexport, dllimport) 네이티브 진입점이 생성되고 해당 함수를 가져오고 호출하는 모든 함수가 네이티브 진입점을 통해 호출됩니다. 이 상황에서 이중 펑크를 방지하려면 네이티브 내보내기/가져오기 의미 체계를 사용하지 마세요. 를 통해 #using
메타데이터를 참조하기만 하면 됩니다(#using 지시문 참조).
불필요한 이중 펑크를 줄이기 위해 컴파일러가 업데이트되었습니다. 예를 들어 서명에 관리되는 형식이 있는 함수(반환 형식 포함)는 암시적으로 로 __clrcall
표시됩니다.
예: 이중 unking
설명
다음 샘플에서는 이중 펑킹을 보여 줍니다. 네이티브(/clr 제외)를 컴파일할 때 가상 함수를 main
호출하면 '의 복사 생성자에 대한 T
호출과 소멸자에 대한 호출이 하나씩 생성됩니다. /clr 및 __clrcall
.을 사용하여 가상 함수를 선언할 때도 비슷한 동작이 수행됩니다. 그러나 /clr로 컴파일된 경우 함수 호출은 복사 생성자에 대한 호출을 생성하지만 네이티브에서 관리되는 thunk로 인해 복사 생성자에 대한 또 다른 호출이 있습니다.
코드
// 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)
예: 이중 unking의 효과
설명
이전 샘플은 이중 렁크의 존재를 보여 주었다. 이 샘플에서는 해당 효과를 보여 있습니다. 루프는 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