I'm calling managed code from unmanaged code using a delegate. When I call into managed code in the default AppDomain I'm measuring an average of 5.4ns per call. When I calling to a second AppDomain I'm measuring 194ns per call. (default VS2017 x86 release configuration, not running under the debugger).
Why is performance so much lower when calling into an AppDomain that isn't the default? Since I'm coming from the unmanaged side, which has no knowledge of AppDomains I would expect to be calling straight into the target domain. However, the performance hit would imply that the delegate is calling into the default domain then marshaling to the real target. I do see UM2MDoADCallBack
when stepping through the disassembly. Which shows up under WrongAppDomain:
in UMThunkStub.asm
How can I prevent this unnecessary marshaling and call directly into a specific AppDomain?
The code I'm using to test this is below.
#pragma unmanaged
#include <wtypes.h>
#include <cstdint>
#include <cwchar>
typedef void (__stdcall *ManagedUpdatePtr)();
struct ProfileSample
{
static uint64_t frequency;
uint64_t startTick;
wchar_t* name;
int count;
ProfileSample(wchar_t* name_, int count_)
{
name = name_;
count = count_;
LARGE_INTEGER win32_startTick;
QueryPerformanceCounter(&win32_startTick);
startTick = win32_startTick.QuadPart;
}
~ProfileSample()
{
LARGE_INTEGER win32_endTick;
QueryPerformanceCounter(&win32_endTick);
uint64_t endTick = win32_endTick.QuadPart;
uint64_t deltaTicks = endTick - startTick;
double nanoseconds = (double) deltaTicks / (double) frequency * 1000000000.0 / count;
wchar_t buffer[128];
swprintf(buffer, _countof(buffer), L"%s - %.4f ns\n", name, nanoseconds);
OutputDebugStringW(buffer);
if (!IsDebuggerPresent())
MessageBoxW(nullptr, buffer, nullptr, 0);
}
};
uint64_t ProfileSample::frequency = 0;
int CALLBACK
WinMain(HINSTANCE, HINSTANCE, PSTR, INT)
{
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
ProfileSample::frequency = frequency.QuadPart;
ManagedUpdatePtr GetManagedUpdatePtr();
auto managedUpdate = GetManagedUpdatePtr();
//Warm stuff up
for ( size_t i = 0; i < 100; i++ )
managedUpdate();
const int num = 10000000;
{
ProfileSample p(L"ManagedUpdate", num);
for ( size_t i = 0; i < num; i++ )
managedUpdate();
}
return 0;
}
#pragma managed
using namespace System;
using namespace System::Diagnostics;
using namespace System::Runtime::InteropServices;
ref struct ManagedObject : MarshalByRefObject
{
ManagedUpdatePtr
GetManagedUpdatePtr()
{
auto delegate = gcnew Action(this, &ManagedObject::ManagedUpdate);
IntPtr fPtr = Marshal::GetFunctionPointerForDelegate(delegate);
return (ManagedUpdatePtr) fPtr.ToPointer();
}
void ManagedUpdate()
{
//Debug::WriteLine("\n\nManagedUpdate ({0})", (Object^) AppDomain::CurrentDomain->FriendlyName);
}
};
ManagedUpdatePtr
GetManagedUpdatePtr()
{
auto pluginDomain = AppDomain::CreateDomain("Plugin Domain");
auto managedObject = (ManagedObject^) pluginDomain->CreateInstanceAndUnwrap("ManagedHelper", "ManagedObject");
return managedObject->GetManagedUpdatePtr();
}