jeudi 11 février 2016

How to properly assess performance gain of CreateDelegate instead of pure reflection?

In a web application I'm working on I have a class that Map objects to other (different) objects. It is implemented with something like:

public interface IMapper
{
    object Map<T>(T item);
}

public class Mapper
{
    private static ConcurrentDictionary<Type, Delegate> cache = new ConcurrentDictionary<Type, Delegate>();

    public Mapper()
    {
        // dependencies are injected in the constructor
        // not shown here for simplicity
    }

    public object Map<T>(T item)
    {
        if (cache.ContainsKey(typeof(T)))
            return (cache[typeof(T)] as Func<T, object>)(item);

        var method = typeof(Mapper).GetMethod("Map", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(T) }, null);

        var @delegate = (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), this, method);
        cache.TryAdd(typeof(T), @delegate);

        return @delegate(item);

        // I'm trying not to use this 
        //return method.Invoke(this, new object[] { item });
    }

    // example methods
    public object Map(SomeObject item) { return new object(); }

    public object Map(SomeTotallyDifferentObject item) { return new object(); }
}

In order to assess what I believe should be a performance gain (compared to the .Invoke() solution) I tried to generate ten million objects (tested with strings) and then I ran the mapper for all of them and printed out the time elapsed. The results are.

  • Direct call: 1.67 s
  • Reflection: 14.09 s
  • Optimized Reflection: 2.62 s

which is not bad actually but I wonder if I'm testing this correctly.

Test code

 var guidList = new List<Guid>();
        for(int i = 0; i < 10000000; i++)
        {
            guidList.Add(Guid.NewGuid());
        }

        var mapper = new Mapper();

        var sw = new Stopwatch();
        sw.Start();
        var result = stringList.Select(_ => mapper.Map<Guid>(_)).ToList();
        sw.Stop();

(Obviously, when I tested the direct method call I changed the select lambda expression to _ => mapper.Map(_))

Am I doing it right?

On a side note: I declared the ConcurrentDictionary as ConcurrentDictionary<Type, Delegate> and so I have to "cast" the delegate part like this (cache[typeof(T)] as Func<T, object>)(item) when I'm using it. Is there a better way to do this?





Aucun commentaire:

Enregistrer un commentaire