Investigation of the method call speed in various ways
Result and conclusions for those who do not like long text
| 100,000 calls, 20 iterations of the test, x86 | 100,000 calls, 20 iterations of the test, x64 | 1,000,000 calls, 10 test iterations, x86 | 1,000,000 calls, 10 test iterations, x64 | |
|---|---|---|---|---|
| Direct call | Min: 1 ms Max: 1 ms Mean: 1 ms Median: 1 ms Abs: 1 | Min: 1 ms Max: 1 ms Mean: 1 ms Median: 1 ms Abs: 1 | Min: 7 ms Max: 8 ms Mean: 7.5 ms Median: 7.5 ms Abs: 1 | Min: 5 ms Max: 6 ms Mean: 5.2 ms Median: 5 ms Abs: 1 |
| Call through reflection | Min: 32 ms Max: 36 ms Mean: 32.75 ms Median: 32.5 ms Rel: 32 | Min: 35 ms Max: 44 ms Mean: 36.5 ms Median: 36 ms Rel: 36 | Min: 333 ms Max: 399 ms Mean: 345.5 ms Median: 338 ms Rel: 45 | Min: 362 ms Max: 385 ms Mean: 373.6 ms Median: 376 ms Rel: 75 |
| Delegate Call | Min: 64 ms Max: 71 ms Mean: 65.05 ms Median: 64.5 ms Rel: 64 | Min: 72 ms Max: 86 ms Mean: 75.95 ms Median: 75 ms Rel: 75 | Min: 659 ms Max: 730 ms Mean: 688.8 ms Median: 689.5 ms Rel: 92 | Min: 746 ms Max: 869 ms Mean: 773.4 ms Median: 765 ms Rel: 153 |
| Call through delegate with optimizations | Min: 16 ms Max: 18 ms Mean: 16.2 ms Median: 16 ms Rel: 16 | Min: 21 ms Max: 25 ms Mean: 22.15 ms Median: 22 ms Rel: 22 | Min: 168 ms Max: 187 ms Mean: 172.8 ms Median: 170.5 ms Rel: 22.7 | Min: 218 ms Max: 245 ms Mean: 228.8 ms Median: 227 ms Rel: 45.4 |
| Call through dynamic | Min: 11 ms Max: 14 ms Mean: 11.5 ms Median: 11 ms Rel: 11 | Min: 12 ms Max: 14 ms Mean: 12.5 ms Median: 12 ms Rel: 12 | Min: 124 ms Max: 147 ms Mean: 132.1 ms Median: 130 ms Rel: 17 | Min: 127 ms Max: 144 ms Mean: 131.5 ms Median: 129.5 ms Rel: 26 |
| Call via Expression | Min: 4 ms Max: 4 ms Mean: 4 ms Median: 4 ms Rel: 4 | Min: 4 ms Max: 5 ms Mean: 4.15 ms Median: 4 ms Rel: 4 | Min: 46 ms Max: 55 ms Mean: 50 ms Median: 50.5 ms Rel: 6.7 | Min: 47 ms Max: 51 ms Mean: 47.7 ms Median: 47 ms Rel: 9.4 |
UPD: a new conclusion from mayorovp : it is best to use Expression
UPD: and as CdEmON suggested , such a study was published on the hub
class SampleGeneric
{
public long Process(T obj)
{
return String.Format("{0} [{1}]", obj.ToString(), obj.GetType().FullName).Length;
}
}
class Container
{
private static Dictionary _instances = new Dictionary();
public static void Register(SampleGeneric instance)
{
if (false == _instances.ContainsKey(typeof(T)))
{
_instances.Add(typeof(T), instance);
}
else
{
_instances[typeof(T)] = instance;
}
}
public static SampleGeneric Get()
{
if (false == _instances.ContainsKey(typeof(T))) throw new KeyNotFoundException();
return (SampleGeneric)_instances[typeof(T)];
}
public static object Get(Type type)
{
if (false == _instances.ContainsKey(type)) throw new KeyNotFoundException();
return _instances[type];
}
}
Similar code is used quite often, and there is one disadvantage in it - in C # you cannot store a collection of generic types explicitly. All the advice that I found comes down to highlighting the base non-generic class, interface or abstract class, which will be indicated for storage. Those. we get something like this:
public interface ISampleGeneric { }
class SampleGeneric : ISampleGeneric
//
private static Dictionary _instances = new Dictionary();
In my opinion, it would be convenient to add the ability to write in this way:
// Ошибка Type expected
Dictionary>
Especially considering that when making a generic type through reflection, we use a similar construction:
typeof(SampleGeneric<>).MakeGenericType(typeof(string))
But back to the problem. Now imagine that we need to get a specific instance, but we need to get it in a non-generic method. For example, a method that accepts an object and based on its type must select a handler.
void NonGenericMethod(object obj)
{
var handler = Container.Get(obj.GetType());
}
We will get the handler, but now the environment will not allow handler.Process (obj) to be written, and if we write, the compiler will swear at the absence of such a method.
Here, too, there could be a design like from C # developers:
Container.GetInstance().Process(obj);
, but it doesn’t exist, and a method must be called (although considering Roslyn, can it already be similar in the new IDEs?). There are many ways to do this, of which several main ones can be distinguished. they are listed in the table at the beginning of the article.
About the code
Below is the call to the code given in the spoiler. Measurements were removed from the code, measurements were taken through Stopwatch. For analysis, I was interested in relative runtime, and not absolute, so iron and other parameters are not important. Tested on different PCs, the results are similar.
It is also worth noting that calls do not take into account the preprocessing time, in which we get to the desired method, because in real conditions, in highly loaded tasks, such actions are performed only once, and the result is cached, so this time does not matter in the analysis.
Direct call
Just pull the method directly. In the table, the results of a direct call in the first row, the Abs value, respectively, is always one, relative to it in the remaining rows you can see a slowdown in calls by other methods of the method call (in the Rel value).
public static TestResult TestDirectCall(DateTime arg)
{
var instance = Container.Get();
long summ = 0;
for (long i = 0; i < ITERATION_COUNT; i++)
{
summ += instance.Process(arg);
}
// return
}
Call through Reflection
The easiest and most affordable way that you want to use in the first place. They took the method from the method table and pull it through Invoke. At the same time, one of the slowest ways.
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
}
Call through delegate and through delegate with additional optimization
Code for creating a delegate
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();
}
Accordingly, the test code becomes as follows:
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
}
We got a slowdown compared to the Reflection method twice as much, you could throw this method, but there is a great way to speed up the process. Honestly, I spied on him in the Impromptu project , namely in this place .
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
}
}
Slightly change the test code
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
}
And we get tenfold acceleration compared to the usual delegate call. At the moment, this is the best option among those considered.
Call through dynamic
And move on to the main character (unless of course you support legacy projects created before .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
}
All that we did in comparison with the call through the delegate, we added the dynamic keyword, which allowed the runtime to build the delegate call through the DLR itself during operation . In fact, they threw type checking checks. And they accelerated twice as compared to the optimized call of delegates.
UPD: Added a more efficient way to call at the prompt mayorovp . It shows the best results in speed, and eats less memory in comparison with dynamic.
delegate object Invoker(object target, params object[] args);
static Invoker CreateExpression(MethodInfo method)
{
var targetArg = Expression.Parameter(typeof(object));
var argsArg = Expression.Parameter(typeof(object[]));
Expression body = Expression.Call(
method.IsStatic ? null : Expression.Convert(targetArg, method.DeclaringType),
method,
method.GetParameters().Select((p, i) => Expression.Convert(Expression.ArrayIndex(argsArg, Expression.Constant(i)), p.ParameterType)));
if (body.Type == typeof(void))
body = Expression.Block(body, Expression.Constant(null));
else if (body.Type.IsValueType)
body = Expression.Convert(body, typeof(object));
return Expression.Lambda(body, targetArg, argsArg).Compile();
}
Test
public static TestResult TestExpressionCall(object arg)
{
var instance = Container.Get(arg.GetType());
var hook = CreateExpression(instance.GetType().GetMethod("Process"));
long summ = 0;
for (long i = 0; i < ITERATION_COUNT; i++)
{
summ += (long)hook(instance, arg);
}
//return
}
Project Code
Return to Results