基于 x64 调用约定的 CLI 调用
CLI calli on x64 calling convention
Calli 操作码需要调用约定。默认情况下它是 stdcall
,而本机库中的 extern "C"
使用 cdecl
.
JIT recently allowed 使用 calli
内联方法,但仅使用 默认调用约定 。当我使用 calli
而不使用 unmanaged cdecl
调用方法时,它在 x64
上运行,性能比 DllImport
快 58%,比 unmanaged function pointer
快 2.2 倍。 (在 netcoreapp2.1
上,在 net471
上差异更大:82% 和 5.5x )当我 运行 使用 calli unmanaged cdecl
的方法时,性能与 [=18= 相当](大约慢 1%)。
我 have read 在 x64 上不再有 stdcall
与 cdecl
的混乱,所有方法都使用 cdecl
(或 fastcall
,看到在另一个地方,找不到 link)。差异仅适用于 x86
,其中我没有 unmanaged cdecl
的调用确实使应用程序因段错误而崩溃。
有问题的方法是the following。对于测试,我仅使用 noop 本机方法来测量本机调用开销。
.method public hidebysig static int32 CalliCompress(uint8* source, native int sourceLength, uint8* destination, native int destinationLength, int32 clevel, native int functionPtr) cil managed aggressiveinlining
{
.custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor()
= {}
//
.maxstack 6
ldarg.0
ldarg.1
ldarg.2
ldarg.3
ldarg 4
ldarg 5
calli unmanaged cdecl int32 (uint8* source, native int sourceLength, uint8* destination, native int destinationLength, int32 clevel)
ret
}
我的问题:
1) 在 x64
"by design" 上的 calli
之后省略 unmanaged cdecl
是否安全,或者我对这个例子很幸运?如果在 x64 上所有调用都是 cdecl
那么我可以使用 JIT treating static readonly
fields as constants 仅使用 if(IntPtr.Size == 8) {..call fast method..}else{..use unmanaged cdecl..}
免费分派到适当的方法
2) caller or callee cleans the stack
是什么意思?我的本机函数 return 是调用后位于堆栈上的 int
。这是关于谁从堆栈中删除此 int
的问题吗?或者还有一些其他的工作需要在本机函数中使用堆栈来完成?我控制着本机函数并且可以 return 通过 ref 参数的值 - 这是否会使堆栈清理问题变得无关紧要,因为在调用期间没有进行堆栈更改?
答案是,不安全。请参阅 dotnet/coreclr 上的讨论:https://github.com/dotnet/coreclr/issues/19997
Calli 操作码需要调用约定。默认情况下它是 stdcall
,而本机库中的 extern "C"
使用 cdecl
.
JIT recently allowed 使用 calli
内联方法,但仅使用 默认调用约定 。当我使用 calli
而不使用 unmanaged cdecl
调用方法时,它在 x64
上运行,性能比 DllImport
快 58%,比 unmanaged function pointer
快 2.2 倍。 (在 netcoreapp2.1
上,在 net471
上差异更大:82% 和 5.5x )当我 运行 使用 calli unmanaged cdecl
的方法时,性能与 [=18= 相当](大约慢 1%)。
我 have read 在 x64 上不再有 stdcall
与 cdecl
的混乱,所有方法都使用 cdecl
(或 fastcall
,看到在另一个地方,找不到 link)。差异仅适用于 x86
,其中我没有 unmanaged cdecl
的调用确实使应用程序因段错误而崩溃。
有问题的方法是the following。对于测试,我仅使用 noop 本机方法来测量本机调用开销。
.method public hidebysig static int32 CalliCompress(uint8* source, native int sourceLength, uint8* destination, native int destinationLength, int32 clevel, native int functionPtr) cil managed aggressiveinlining
{
.custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor()
= {}
//
.maxstack 6
ldarg.0
ldarg.1
ldarg.2
ldarg.3
ldarg 4
ldarg 5
calli unmanaged cdecl int32 (uint8* source, native int sourceLength, uint8* destination, native int destinationLength, int32 clevel)
ret
}
我的问题:
1) 在 x64
"by design" 上的 calli
之后省略 unmanaged cdecl
是否安全,或者我对这个例子很幸运?如果在 x64 上所有调用都是 cdecl
那么我可以使用 JIT treating static readonly
fields as constants 仅使用 if(IntPtr.Size == 8) {..call fast method..}else{..use unmanaged cdecl..}
2) caller or callee cleans the stack
是什么意思?我的本机函数 return 是调用后位于堆栈上的 int
。这是关于谁从堆栈中删除此 int
的问题吗?或者还有一些其他的工作需要在本机函数中使用堆栈来完成?我控制着本机函数并且可以 return 通过 ref 参数的值 - 这是否会使堆栈清理问题变得无关紧要,因为在调用期间没有进行堆栈更改?
答案是,不安全。请参阅 dotnet/coreclr 上的讨论:https://github.com/dotnet/coreclr/issues/19997