20110511

even better type conversion between similar objects

My last post showed how to use compiled linq expressions as delegates to make a type converter.  I noted that an MSIL solution would be faster.  I am not a MSIL expert by any means, but here is my best attempt as using DynamicMethod and MSIL to create a delegate converter.

It does the same thing as the linq version but requires separate assign blocks for each combination of field and property.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;

namespace typeoverridedemostrater
{

    static class SuperConverter<T1, T2> where T2 : new()
    {

        private static Dictionary<Tuple<System.Type, System.Type>, MethodInvoker> converters = new Dictionary<Tuple<System.Type, System.Type>, MethodInvoker>();
        delegate T2 MethodInvoker(T1 inpt);

        static public T2 Convert(T1 i)
        {
            var key = new Tuple<System.Type, System.Type>(typeof(T2), typeof(T1));
            if (!converters.ContainsKey(key))
            {
                DynamicMethod dm = new DynamicMethod("xmethod", typeof(T2), new Type[] { typeof(T1) }, typeof(T2));
                ILGenerator il = dm.GetILGenerator();
                il.DeclareLocal(typeof(T2));
                il.Emit(OpCodes.Newobj, typeof(T2).GetConstructor(System.Type.EmptyTypes));
                il.Emit(OpCodes.Stloc_0);

                (from z in typeof(T1).GetFields().Select(a => new { name = a.Name, type = a.FieldType })
                 join y in typeof(T2).GetFields().Select(a => new { name = a.Name, type = a.FieldType })
                 on z equals y
                 select z.name).ToList().ForEach(a =>
                 {
                     il.Emit(OpCodes.Ldloc_0);
                     il.Emit(OpCodes.Ldarg_0);
                     il.Emit(OpCodes.Ldfld, typeof(T1).GetField(a));
                     il.Emit(OpCodes.Stfld, typeof(T2).GetField(a));
                 });

                (from z in typeof(T1).GetProperties().Select(a => new { name = a.Name, type = a.PropertyType })
                 join y in typeof(T2).GetProperties().Where(b => b.CanWrite).Select(a => new { name = a.Name, type = a.PropertyType })
                 on z equals y
                 select z.name).ToList().ForEach(a =>
                 {
                     il.Emit(OpCodes.Ldloc_0);
                     il.Emit(OpCodes.Ldarg_0);
                     il.Emit(OpCodes.Callvirt, typeof(T1).GetProperty(a).GetGetMethod());
                     il.Emit(OpCodes.Callvirt, typeof(T2).GetProperty(a).GetSetMethod());
                 });


                (from z in typeof(T1).GetFields().Select(a => new { name = a.Name, type = a.FieldType })
                 join y in typeof(T2).GetProperties().Where(b => b.CanWrite).Select(a => new { name = a.Name, type = a.PropertyType })
                 on z equals y
                 select z.name).ToList().ForEach(a =>
                 {
                     il.Emit(OpCodes.Ldloc_0);
                     il.Emit(OpCodes.Ldarg_0);
                     il.Emit(OpCodes.Ldfld, typeof(T1).GetField(a));
                     il.Emit(OpCodes.Callvirt, typeof(T2).GetProperty(a).GetSetMethod());
                 });

                (from z in typeof(T1).GetProperties().Select(a => new { name = a.Name, type = a.PropertyType })
                 join y in typeof(T2).GetFields().Select(a => new { name = a.Name, type = a.FieldType })
                 on z equals y
                 select z.name).ToList().ForEach(a =>
                 {
                     il.Emit(OpCodes.Ldloc_0);
                     il.Emit(OpCodes.Ldarg_0);
                     il.Emit(OpCodes.Callvirt, typeof(T1).GetProperty(a).GetGetMethod());
                     il.Emit(OpCodes.Stfld, typeof(T2).GetField(a));
                 });

                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ret);

                var dg = (MethodInvoker)dm.CreateDelegate(typeof(MethodInvoker));

                converters.Add(key, dg);
            }

            return (T2)converters[key](i);
        }
    }
}

Compared to the Linq version at 100 million repetitions (at lower numbers the results are less predictable):

Linq: 94.579 seconds vs MSIL: 93.423 seconds


Some of that time is consumed dealing with the delegates, so you can transform that into a full dynamic assembly where you can have base classes and real instance references like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Threading;
using System.Reflection;


namespace typeoverridedemostrater
{

    static class SuperConverterII<T1, T2> where T2 : new()
    {

        private static Dictionary<Tuple<System.Type, System.Type>, convertbase<T1, T2>> converters = new Dictionary<Tuple<System.Type, System.Type>, convertbase<T1, T2>>();

        static public T2 Convert(T1 i)
        {
            var key = new Tuple<System.Type, System.Type>(typeof(T2), typeof(T1));
            if (!converters.ContainsKey(key))
            {

                var xassemblybuilder = Thread.GetDomain().DefineDynamicAssembly(new AssemblyName("xassembly"), System.Reflection.Emit.AssemblyBuilderAccess.Run);
                var xtype = xassemblybuilder.DefineDynamicModule("xmodule").DefineType("xtype", TypeAttributes.Public, typeof(convertbase<T1, T2>));
                var xfunction = xtype.DefineMethod("convert", MethodAttributes.Public | MethodAttributes.Virtual, typeof(T2), new System.Type[] { typeof(T1) });
                xtype.DefineMethodOverride(xfunction, typeof(convertbase<T1, T2>).GetMethod("convert"));

                var il = xfunction.GetILGenerator();

                il.DeclareLocal(typeof(T2));

                il.Emit(OpCodes.Newobj, typeof(T2).GetConstructor(System.Type.EmptyTypes));
                il.Emit(OpCodes.Stloc_0);

                (from z in typeof(T1).GetFields().Select(a => new { name = a.Name, type = a.FieldType })
                 join y in typeof(T2).GetFields().Select(a => new { name = a.Name, type = a.FieldType })
                 on z equals y
                 select z.name).ToList().ForEach(a =>
                 {
                     il.Emit(OpCodes.Ldloc_0);
                     il.Emit(OpCodes.Ldarg_1);
                     il.Emit(OpCodes.Ldfld, typeof(T1).GetField(a));
                     il.Emit(OpCodes.Stfld, typeof(T2).GetField(a));
                 });

                (from z in typeof(T1).GetProperties().Select(a => new { name = a.Name, type = a.PropertyType })
                 join y in typeof(T2).GetProperties().Where(b => b.CanWrite).Select(a => new { name = a.Name, type = a.PropertyType })
                 on z equals y
                 select z.name).ToList().ForEach(a =>
                 {
                     il.Emit(OpCodes.Ldloc_0);
                     il.Emit(OpCodes.Ldarg_1);
                     il.Emit(OpCodes.Callvirt, typeof(T1).GetProperty(a).GetGetMethod());
                     il.Emit(OpCodes.Callvirt, typeof(T2).GetProperty(a).GetSetMethod());
                 });


                (from z in typeof(T1).GetFields().Select(a => new { name = a.Name, type = a.FieldType })
                 join y in typeof(T2).GetProperties().Where(b => b.CanWrite).Select(a => new { name = a.Name, type = a.PropertyType })
                 on z equals y
                 select z.name).ToList().ForEach(a =>
                 {
                     il.Emit(OpCodes.Ldloc_0);
                     il.Emit(OpCodes.Ldarg_1);
                     il.Emit(OpCodes.Ldfld, typeof(T1).GetField(a));
                     il.Emit(OpCodes.Callvirt, typeof(T2).GetProperty(a).GetSetMethod());
                 });

                (from z in typeof(T1).GetProperties().Select(a => new { name = a.Name, type = a.PropertyType })
                 join y in typeof(T2).GetFields().Select(a => new { name = a.Name, type = a.FieldType })
                 on z equals y
                 select z.name).ToList().ForEach(a =>
                 {
                     il.Emit(OpCodes.Ldloc_0);
                     il.Emit(OpCodes.Ldarg_1);
                     il.Emit(OpCodes.Callvirt, typeof(T1).GetProperty(a).GetGetMethod());
                     il.Emit(OpCodes.Stfld, typeof(T2).GetField(a));
                 });

                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ret);

                xtype.CreateType();

                converters.Add(key, (convertbase<T1, T2>)xassemblybuilder.CreateInstance("xtype"));
            }
            return converters[key].convert(i);
        }

    }

    public abstract class convertbase<T1, T2>
    {
        public abstract T2 convert(T1 i);
    }
}

Compared to the original MSIL version at 100 million repetitions:

Delegate MSIL: 93.423 seconds vs Base Class MSIL: 91.814 seconds

At REALLY high volumes that is significant, and I am certain that someone better at MSIL than I could probably make it a bit faster still, but for my purposes the Linq version is more than sufficient as I find the MSIL versions a little hard to work in for the level of benefit in return.

2 comments:

  1. Nice work, Bill. And if I'm shaving a 1/2 second off of doing 100 million anything, I'd consider changing jobs.

    ReplyDelete
  2. IMHO It depends on if you are doing it because it would be nice or if you are doing it to try and save your butt...

    When I started this third approach I actually expected that the MSIL versions should outperform the Linq versions quite a bit more than they did. I may have done something wrong. I am not really that good with the MSIL stuff yet.

    ReplyDelete

Firefox Feedly RSS option

If you use Firefox with a RSS button and want the default RSS page to offer a Feedly option here is what you need to do: go to the about:c...