Почему Calli быстрее делегатского звонка?
Я играл с Reflection.Emit и узнал о малоиспользуемых 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-бит:
- Версия делегата: 994
- Калли версия: 46
64-бит:
- Версия делегата: 326
- Калли версия: 83
Я думаю, вопрос к настоящему времени очевиден... почему такая огромная разница в скорости?
Обновить:
Я также создал 64-битную версию P/Invoke:
- Версия делегата: 284
- Калли версия: 77
- P/Invoke версия: 31
По-видимому, P/Invoke быстрее... это проблема с моим сравнительным тестированием, или что-то происходит, я не понимаю? (Кстати, я нахожусь в режиме релиза.)
2 ответа
Учитывая ваши показатели производительности, я полагаю, вы используете фреймворк 2.0 или что-то подобное? Цифры намного лучше в 4.0, но версия Marshal.GetDelegate все еще медленнее.
Дело в том, что не все делегаты созданы равными.
Делегаты для функций управляемого кода - это, по сути, просто прямой вызов функции (в x86 это __fastcall) с добавлением небольшого "switcheroo", если вы вызываете статическую функцию (но это всего лишь 3 или 4 инструкции для x86).
С другой стороны, делегаты, созданные с помощью Marshal.GetDelegateForFunctionPointer, - это прямой вызов функции в функцию-заглушку, которая выполняет небольшие накладные расходы (сортировка и еще много чего) перед вызовом неуправляемой функции. В этом случае сортировка очень мала, и сортировка для этого вызова, по-видимому, в значительной степени оптимизирована в 4.0 (но, скорее всего, она все еще проходит через интерпретатор ML на 2.0), но даже в 4.0 есть stackWalk, требующий разрешений неуправляемого кода, которые не является частью вашего делегата калли
Обычно я обнаружил, что, если не знать кого-то из команды разработчиков.NET, вам лучше всего понять, что происходит с управляемым / неуправляемым взаимодействием, - это немного покопаться с WinDbg и SOS.
Сложно ответить:) В любом случае я постараюсь.
EmitCalli быстрее, потому что это вызов необработанного байтового кода. Я подозреваю, что SuppressUnmanagedCodeSecurity также отключит некоторые проверки, например, переполнение стека / массив вне проверок индекса границ. Так что код небезопасен и работает на полной скорости.
Версия делегата будет иметь некоторый скомпилированный код для проверки набора текста, а также будет выполнять вызов де-ссылки (потому что делегат похож на указатель на типизированную функцию).
Мои два цента!