В разработке приложений на C# иногда возникает необходимость вызsвать методы, поступающие к нам в виде MethodInfo. Самым простым способом это осуществить является вызов метода MethodInfo.Invoke(object? obj, object?[]? parameters), но так ли он быстр?
Давайте рассмотрим этот вопрос и найдем все альтернативы этому способу. Для этого мы напишем небольшой бенчмарк, используя библиотеку BenchmarkDotNet. В замерах мы будем учитывать вызов метода 1 раз и 1000 раз.
1) MethodInfo.Invoke какой он есть.
В замерах на 1 вызов он выполняется за 29 ns.
В замерах на 1000 вызовов он выполняется за 27,600 ns.
Этот код выделяет 88 байт при вызове, где по 16 байт уходит на boxing структур и остальное на инициализацию массива.
2) Вызов через Delegate
В замерах на 1 вызов он выполняется за 280 ns .
В замерах на 1000 вызовов он выполняется за 2,360 ns.
Данный вариант создаст единожды 64 байт на аллокацию делегата.
Этот вариант довольно эффективен среди всех "safe" способов, если будет закеширован для дальнейших вызовов.
3) Вызов через указатель на функция
В замерах на 1 вызов он выполняется за 18 ns .
В замерах на 1000 вызовов он выполняется за 1,622 ns.
Этот код не выделяет никакой памяти.
По скорости почти как вызвать этот метод напрямую, но увы, некоторые не особо любят "unsafe".
4) Вызов MethodInfo.Invoke.
Идеальный компромисс между "unsafe" и "safe". (исходный код ObjectParamsArray и Box<T> будет в конце статьи)
В замерах на 1 вызов он выполняется за 20 ns .
В замерах на 1000 вызовов он выполняется за 18,823 ns.
Этот код не выделяет никакой памяти.
Этот вариант второй по скорости на 1 вызов и 3 по скорости на 1000 вызовов.
Вся магия происходит за счет мимикризации структур под соответствующим им объекты. В .NET boxed структуры имеют следующий вид: [ 0x0...0x8 - указатель на MethodTable объекта; 0x8...(0x8 + sizeof(Структуры) ]. Соответственно, чтобы выдать нашу структуру за boxed структуру, нам надо поместить в начало структуры указатель на нее! Само собой надо нашей псевдо-структуре добавить атрибут StructLayout(Sequential), чтобы jit компилятор не утащил указатель на MethodTable куда-нибудь в конец.
Остается дело за малым, превратить указатель на структуру в object. В .NET 8 это достигается обычным Unsafe.BitCast указателя в object.
Общие результаты опыта:
Ссылка на код бенчмарка: https://gist.github.com/BadRyuner/602a94b1f3b4e054258dbec165f24d68
Всем спасибо за внимание!