C# 为什么Calli比代表电话快?
我在玩反射。发射和发现很少使用。出于好奇,我想知道它是否与常规方法调用有所不同,因此我编写了以下代码:C# 为什么Calli比代表电话快?,c#,.net,reflection.emit,ilgenerator,C#,.net,Reflection.emit,Ilgenerator,我在玩反射。发射和发现很少使用。出于好奇,我想知道它是否与常规方法调用有所不同,因此我编写了以下代码: using System; using System.Diagnostics; using System.Reflection.Emit; using System.Runtime.InteropServices; using System.Security; [SuppressUnmanagedCodeSecurity] static class Program { const l
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);
}
使用系统;
使用系统诊断;
使用System.Reflection.Emit;
使用System.Runtime.InteropServices;
使用系统安全;
[SuppressUnmanagedCodeSecurity]
静态类程序
{
常量长计数=1难以回答:)
无论如何,我会努力的
EmitCalli更快,因为它是一个原始字节码调用。我怀疑SuppressUnmanagedCodeSecurity还会禁用一些检查,例如堆栈溢出/数组越界索引检查。因此代码不安全,无法全速运行
委托版本将有一些已编译的代码来检查类型,并且还将执行反引用调用(因为委托类似于类型化函数指针)
我的两分钱!考虑到您的性能数字,我想您一定是在使用2.0框架或类似的东西?4.0中的数字要好得多,但“Marshal.GetDelegate”版本仍然较慢
问题是,并非所有代理都是平等创建的
托管代码函数的委托本质上只是一个直接的函数调用(在x86上,这是一个_快速调用),如果调用的是静态函数,则会添加一点“switcheroo”(但在x86上只有3或4条指令)
另一方面,由“Marshal.GetDelegateForFunctionPointer”创建的委托是对“stub”函数的直接函数调用,这会产生一些开销(编组等)在调用非托管函数之前。在这种情况下,编组非常少,并且在4.0中,此调用的编组似乎得到了极大的优化(但最有可能的情况是在2.0中仍然通过ML解释器)-但即使在4.0中,也有一个stackWalk要求非托管代码权限,而该权限不属于calli委托的一部分
我通常发现,在不了解.NET开发团队成员的情况下,要想了解托管/非托管互操作的情况,最好的办法是对WinDbg和SOS进行一些挖掘。这是一个非常有趣的问题。我也在机器上进行了尝试,速度差异很大。我还想知道背后的确切原因。我很好奇事实上,我开始怀疑我的标杆可能是错误的——可能中间有一些我没有注意到的指令,这会使结果变得混乱。不过,现在我想不到太多了。嗯……我有点困惑:注意两个版本都使用一个委托,这只是代表如何做的一个区别。所以两者都不应该。在这方面也是一样的。SuppressUnmanagedCodeSecurity
并没有禁用类型检查安全性,它执行从托管到非托管转换的堆栈遍历。这是为了非托管代码特权;我把它放在那里是为了消除不相关的瓶颈。@mehrdad:你说得对,但速度受到影响:我们从“此属性可应用于希望调用本机代码而不会导致运行时安全检查性能损失的方法。“但是如果它在我的代码中禁用了对所有转换的检查,那么为什么会有不同呢?@mehrdad:这一点很好。我不知道。正如我所说,委托编译的版本应该有一些更间接的调用(对于isntance,它至少需要一个指针取消引用).速度会慢一些,但不会太快。谜团还在这里!这家伙知道他说了什么!+1000