Результат и выводы для тех кто не любит длинный текст
100.000 вызовов, 20 итераций теста, x86 | 100.000 вызовов, 20 итераций теста, x64 | 1.000.000 вызовов, 10 итераций теста, x86 | 1.000.000 вызовов, 10 итераций теста, x64 | |
---|---|---|---|---|
Прямой вызов |
Min: 10 ms Max: 11 ms Mean: 10,15 ms Median: 10 ms Abs: 1
|
min: 5 ms Max: 5 ms Mean: 5 ms Median: 5 ms Abs: 1
|
Min: 107 ms Max: 110 ms Mean: 107,8 ms Median: 107,5 ms Abs: 1
|
Min: 55 ms Max: 57 ms Mean: 55,3 ms Median: 55 ms Abs: 1
|
Вызов через отражение |
Min: 336 ms Max: 455 ms Mean: 359,45 ms Median: 342,5 ms Rel: 34
|
Min: 327 ms Max: 358 ms Mean: 336,1 ms Median: 335 ms Rel: 67
|
Min: 3362 ms Max: 3419 ms Mean: 3387,2 ms Median: 3385 ms Rel: 31
|
Min: 3334 ms Max: 3427 ms Mean: 3370,2 ms Median: 3363 ms Rel: 61
|
Вызов через делегат |
Min: 657 ms Max: 1376 ms Mean: 728,4 ms Median: 684,5 ms Rel: 68
|
Min: 660 ms Max: 726 ms Mean: 686,05 ms Median: 685,5 ms Rel: 137
|
Min: 6586 ms Max: 13735 ms Mean: 7340,6 ms Median: 6632,5 ms Rel: 62
|
Min: 6501 ms Max: 6919 ms Mean: 6798 ms Median: 6828 ms Rel: 124
|
Вызов через делегат с оптимизациями |
Min: 67 ms Max: 86 ms Mean: 69,2 ms Median: 67,5 ms Rel: 6.7
|
Min: 79 ms Max: 88 ms Mean: 80,95 ms Median: 80 ms Rel: 16
|
Min: 683 ms Max: 7373 ms Mean: 1360,7 ms Median: 691 ms Rel: 6.5
|
Min: 775 ms Max: 814 ms Mean: 789,8 ms Median: 783,5 ms Rel: 12
|
Вызов через dynamic |
Min: 47 ms Max: 50 ms Mean: 48 ms Median: 48 ms Rel: 5
|
Min: 39 ms Max: 43 ms Mean: 41,25 ms Median: 41 ms Rel: 8
|
Min: 479 ms Max: 518 ms Mean: 492 ms Median: 487 ms Rel: 4.5
|
Min: 379 ms Max: 420 ms Mean: 392,1 ms Median: 387,5 ms Rel: 7
|
При использованиии .NET Framework 3.5 лучше всего использовать вызов методов через делегат с оптимизацией вызова. Для .NET Framework 4.0+ отличным выбором будет использование dynamic.
class SampleGeneric<T> { public long Process(T obj) { return String.Format("{0} [{1}]", obj.ToString(), obj.GetType().FullName).Length; } } class Container { private static Dictionary<Type, object> _instances = new Dictionary<Type, object>(); public static void Register<T>(SampleGeneric<T> instance) { if (false == _instances.ContainsKey(typeof(T))) { _instances.Add(typeof(T), instance); } else { _instances[typeof(T)] = instance; } } public static SampleGeneric<T> Get<T>() { if (false == _instances.ContainsKey(typeof(T))) throw new KeyNotFoundException(); return (SampleGeneric<T>)_instances[typeof(T)]; } public static object Get(Type type) { if (false == _instances.ContainsKey(type)) throw new KeyNotFoundException(); return _instances[type]; } }
Подобный код используется довольно часто, и в нем есть одно неудобство, — в C# нельзя хранить коллекцию generic типов явным образом. Все советы которые я находил сводятся к выделению базового non-generic класса, интерфейса или абстрактного класса, который и будет указан для хранения. Т.е. получим что-то вроде такого:
public interface ISampleGeneric { } class SampleGeneric<T> : ISampleGeneric // private static Dictionary<Type, ISampleGeneric> _instances = new Dictionary<Type, ISampleGeneric>();
На мой взгляд, было бы удобно добавить в язык возможность писать таким образом:
// Ошибка Type expected Dictionary<Type, SampleGeneric<>>
Особенно учитывая, что делая generic тип через рефлексию, мы пользуемся схожей конструкцией:
typeof(SampleGeneric<>).MakeGenericType(typeof(string))
Но вернемся к проблеме. Теперь представим что нам нужно получить конкретный инстанс, но получить его нам нужно в non-generic методе. Например, метод который принимает объект и исходя из его типа должен подобрать обработчик.
void NonGenericMethod(object obj) { var handler = Container.Get(obj.GetType()); }
Обработчик мы получим, но среда не позволит написать теперь handler.Process(obj), а если и напишем, компилятор ругнется на отсутствие такого метода.
Вот тут тоже могла бы быть от разработчиков C# конструкция наподобие:
Container.GetInstance<fromtype(obj.GetType())>().Process(obj);
, но ее нет, а метод вызвать требуется (хотя учитывая Roslyn может уже есть подобное в новых IDE?). Способов сделать это масса, из которых можно выделить несколько основных. они и перечислены в таблице в начале статьи.
Про код
Ниже используется вызов кода приведенного в спойлере. Из кода убраны замеры времение, замеры делались через Stopwatch. Для анализа интересовало относительное время выполнения, а не абсолютное, поэтому железо и другие параметры не важны. Тестировал на разных пк, результаты схожие.
Также стоит заметить, что при вызовах не учитывается время на предобработку, в которой добираемся до нужного метода, т.к. в реальных условиях, в высоконагруженных задачах такие действия выполняются только раз, и результат кэшируется, соответственно это время не имеет значения при анализе.
Прямой вызов
Просто дергаем метод напрямую. В таблице, результаты прямого вызова в первой строке, значение Abs соответственно всегда единица, относительно него в остальных строках можно видеть замедление вызовов другими способами вызова метода (в значении Rel).
public static TestResult TestDirectCall(DateTime arg) { var instance = Container.Get<DateTime>(); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += instance.Process(arg); } // return }
Вызов через Reflection
Самый простой и доступный способ, который хочется использовать в первую очередь. Забрали метод из таблицы методов и дергаем его через Invoke. В то же время, один из самых медленных способов.
public static TestResult TestReflectionCall(object arg) { var instance = Container.Get(arg.GetType()); var method = instance.GetType().GetMethod("Process"); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += (long)method.Invoke(instance, new object[] { arg }); } // return }
Вызов через делегат и через делегат с дополнительной оптимизацией
private static Delegate CreateDelegate(object target, MethodInfo method) { var methodParameters = method.GetParameters(); var arguments = methodParameters.Select(d => Expression.Parameter(d.ParameterType, d.Name)).ToArray(); var instance = target == null ? null : Expression.Constant(target); var methodCall = Expression.Call(instance, method, arguments); return Expression.Lambda(methodCall, arguments).Compile(); }
Соответственно код теста становится следующим:
public static TestResult TestDelegateCall(object arg) { var instance = Container.Get(arg.GetType()); var hook = CreateDelegate(instance, instance.GetType().GetMethod("Process")); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += (long)hook.DynamicInvoke(arg); } // return }
Получили замедление по сравнению с Reflection способом еще в два раза, можно было бы выкинуть этот метод, но есть отличный способ ускорить процесс. Честно скажу что подсмотрел его в проекте Impromptu, а именно в этом месте.
internal static object FastDynamicInvokeDelegate(Delegate del, params dynamic[] args) { dynamic tDel = del; switch (args.Length) { default: try { return del.DynamicInvoke(args); } catch (TargetInvocationException ex) { throw ex.InnerException; } #region Optimization case 1: return tDel(args[0]); case 2: return tDel(args[0], args[1]); case 3: return tDel(args[0], args[1], args[2]); case 4: return tDel(args[0], args[1], args[2], args[3]); case 5: return tDel(args[0], args[1], args[2], args[3], args[4]); case 6: return tDel(args[0], args[1], args[2], args[3], args[4], args[5]); case 7: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); case 8: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); case 9: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); case 10: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); case 11: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); case 12: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); case 13: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); case 14: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); case 15: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); case 16: return tDel(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]); #endregion } }
Незначительно меняем код теста
public static TestResult TestDelegateOptimizeCall(object arg) { var instance = Container.Get(arg.GetType()); var hook = CreateDelegate(instance, instance.GetType().GetMethod("Process")); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += (long)FastDynamicInvokeDelegate(hook, arg); } // return }
И получаем десятикратное ускорение по сравнению с обычным вызовом делегата. На текущий момент это лучший вариант из рассмотренных.
Вызов через dynamic
И переходим к главному герою (если конечно вы не поддерживаете legacy проекты созданные до .NET 4.0)
public static TestResult TestDynamicCall(dynamic arg) { var instance = Container.Get(arg.GetType()); dynamic hook = CreateDelegate(instance, instance.GetType().GetMethod("Process")); long summ = 0; for (long i = 0; i < ITERATION_COUNT; i++) { summ += hook(arg); } // return }
Все что мы сделали по сравнению с вызовом через делегат, добавили ключевое слово dynamic, чем позволили среде исполнения во время работы самой построить через DLR вызов делегата. По сути выкинули проверки на совпадение типов. И ускорились еще в два раза по сравнению с оптимизированным вызовом делегатов.
ссылка на оригинал статьи https://habrahabr.ru/post/279367/
Добавить комментарий