为什么Calli比委托调用快
本文关键字:调用 Calli 为什么 | 更新日期: 2023-09-27 17:54:42
我正在玩反射。并发现了很少使用的EmitCalli
。我很好奇,想知道它是否与常规的方法调用有什么不同,所以我快速编写了下面的代码:
using System;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Security;
[SuppressUnmanagedCodeSecurity]
static class Program
{
const long COUNT = 1 << 22;
static readonly byte[] multiply = IntPtr.Size == sizeof(int) ?
new byte[] { 0x8B, 0x44, 0x24, 0x04, 0x0F, 0xAF, 0x44, 0x24, 0x08, 0xC3 }
: new byte[] { 0x0f, 0xaf, 0xca, 0x8b, 0xc1, 0xc3 };
static void Main()
{
var handle = GCHandle.Alloc(multiply, GCHandleType.Pinned);
try
{
//Make the native method executable
uint old;
VirtualProtect(handle.AddrOfPinnedObject(),
(IntPtr)multiply.Length, 0x40, out old);
var mulDelegate = (BinaryOp)Marshal.GetDelegateForFunctionPointer(
handle.AddrOfPinnedObject(), typeof(BinaryOp));
var T = typeof(uint); //To avoid redundant typing
//Generate the method
var method = new DynamicMethod("Mul", T,
new Type[] { T, T }, T.Module);
var gen = method.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldc_I8, (long)handle.AddrOfPinnedObject());
gen.Emit(OpCodes.Conv_I);
gen.EmitCalli(OpCodes.Calli, CallingConvention.StdCall,
T, new Type[] { T, T });
gen.Emit(OpCodes.Ret);
var mulCalli = (BinaryOp)method.CreateDelegate(typeof(BinaryOp));
var sw = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++) { mulDelegate(2, 3); }
Console.WriteLine("Delegate: {0:N0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < COUNT; i++) { mulCalli(2, 3); }
Console.WriteLine("Calli: {0:N0}", sw.ElapsedMilliseconds);
}
finally { handle.Free(); }
}
delegate uint BinaryOp(uint a, uint b);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool VirtualProtect(
IntPtr address, IntPtr size, uint protect, out uint oldProtect);
}
我在x86模式和x64模式下运行代码。结果呢?
32位:我想这个问题现在很明显了…为什么会有如此巨大的速度差异?64位:
- 委托版本:994
- Calli版本:46
- 委托版本:326
- Calli版本:83
更新:
我也创建了一个64位的p/Invoke版本:
- 委托版本:284
- Calli version: 77
- P/Invoke version: 31
显然,p/Invoke更快…这是我的基准测试有问题,还是有什么我不明白的地方?(顺便说一下,我处于释放模式)
考虑到您的性能数字,我认为您一定在使用2.0框架,或者类似的东西?4.0版本的数据好多了,但是"元帅"。版本仍然较慢。
问题是并不是所有的委托都是平等的。
托管代码函数的委托本质上只是一个直接的函数调用(在x86上,这是__fastcall),如果你调用静态函数(但在x86上只有3或4条指令),则添加了一点"switcheroo"。
由Marshal创建的委托。另一方面,GetDelegateForFunctionPointer"是一个直接调用到"存根"函数的函数,它在调用非托管函数之前会产生一点开销(封送等)。在这种情况下,很少有编组,并且这个调用的编组似乎在4.0中得到了相当大的优化(但很可能仍然通过2.0的ML解释器)-但即使在4.0中,也有一个stackWalk要求非托管代码权限,这不是调用委托的一部分。
我通常发现,在不认识。net开发团队的人的情况下,要想弄清楚/托管/非托管互操作发生了什么,最好的办法是用WinDbg和SOS做一些挖掘。
很难回答:)不管怎样,我要试试。
EmitCalli更快,因为它是一个原始字节代码调用。我怀疑suppressunmanagedcodessecurity也将禁用一些检查,例如堆栈溢出/数组越界索引检查。因此,代码是不安全的,并在全速运行。委托版本将有一些编译代码来检查类型,并且还将执行取消引用调用(因为委托就像一个类型函数指针)。
我的两分钱!