首页 > 代码库 > Loogn.OrmLite映射优化记录

Loogn.OrmLite映射优化记录

大家对ORM效率的争议多半在映射性能方面。自己的ORMLite也是如此,经过前段时间的折腾,已经找不出一个简单的方法再提升一下这部分的方法了。在此把优化涉及的几点记录一下。

注:用于性能测试的CodeTimer为赵劼先生所写。

注:有两个优化方法为hubro先生所提供,下面会指出。

 

一、反射的优化


简单的说,反射给了我们动态发现类型信息的能力,Type对象是开启反射之路的入口点。从Type对象那里,我们可以得到该类型的PropertyInfo、MethodInfo、ConstructorInfo等等。在ORM映射中,一般都会用到PropertyInfo。我们可以在PropertyInfo上访问(设置和获取统称为访问)对象的这个属性,但由于是动态发现信息再动态调用,所以性能和直接调用差了很多。我们用一个Person类测试一下:

    public class Person    {        public int ID { get; set; }        public string Name { get; set; }    }

测试代码:

        public static void Test()        {            Person person = new Person();            var type = person.GetType();            //先取出来PropertyInfo对象,相当于缓存            var idPropertyInfo = type.GetProperty("ID");            var namePropertyInfo = type.GetProperty("Name");            CodeTimer.Initialize();            var iteration = 1000000;//一百万次            CodeTimer.Time("直接访问", iteration, () =>            {                person.ID = 23;                person.Name = "loogn";            });            CodeTimer.Time("PropertyInfo访问", iteration, () =>            {                idPropertyInfo.SetValue(person, 23);                namePropertyInfo.SetValue(person, "loogn");            });        }

测试结果:

直接访问        Time Elapsed:   12ms        CPU Cycles:     31,415,669        Gen 0:          0        Gen 1:          0        Gen 2:          0PropertyInfo访问        Time Elapsed:   683ms        CPU Cycles:     1,698,787,436        Gen 0:          48        Gen 1:          0        Gen 2:          0请按任意键继续. . .

上面循环了一百万次,尽管对PropertyInfo对象缓存了,调用效率差距还是很大的。后来在网上查询一番,测试一番,最后发现编译成强类型的委托调用起来是最好的(包括用IL生成同等的代码)。

测试代码:

            Person person = new Person();            var type = person.GetType();            //先得出来PropertyInfo对象,相当于缓存            var idPropertyInfo = type.GetProperty("ID");            var namePropertyInfo = type.GetProperty("Name");            //这两个委托也是缓存起来的            var idSetter = (Action<Person, int>)Delegate.CreateDelegate(typeof(Action<Person, int>), null, idPropertyInfo.GetSetMethod(true));            var nameSetter = (Action<Person, string>)Delegate.CreateDelegate(typeof(Action<Person, string>), null, namePropertyInfo.GetSetMethod(true));            CodeTimer.Initialize();            var iteration = 1000000;//一百万次            CodeTimer.Time("直接访问", iteration, () =>            {                person.ID = 23;                person.Name = "loogn";            });            CodeTimer.Time("强类型委托访问", iteration, () =>            {                idSetter(person, 23);                nameSetter(person, "loogn");            });

 

测试结果:

直接访问        Time Elapsed:   11ms        CPU Cycles:     28,167,714        Gen 0:          0        Gen 1:          0        Gen 2:          0强类型委托访问        Time Elapsed:   18ms        CPU Cycles:     45,133,481        Gen 0:          0        Gen 1:          0        Gen 2:          0请按任意键继续. . .

小伙伴儿是不是惊呆了!反正当时我是惊呆啦,效率直逼硬编码了。我是一个很乐观的人,看到这样的测试结果,就幸哉幸哉地以为优化之路到此结束!可是经过测试,和Dapper还差那么一点点,于是就闷闷不乐了.....,于是这篇文章还有下面的部分。

 

二、分支语句与多态


 以前从来没有感觉ifelse会有什么效率问题,但是当分支很多的时候,用多态是一种效率更高而且更容易维护的方案。由于上面用了强类型的委托,赋值的时候就用判断字段的类型了。从数据库的类型对应到C#里,大概有十几种,加上可空类型,分支判断就会很多。这里提一下:判断一个对象的类型的时候尽量用is (比如 obj is int),经过测试,这样比比较Type效率要高。为了让读者明白我具体所指,贴出下面潦草的代码:(由于分支很少,下面代码测试结果并不明显)

技术分享
    public abstract class Parent    {        public abstract void Do(object o);    }    public class Sub1 : Parent    {        public override void Do(object o)        {            var s = (int)o;        }    }    public class Sub2 : Parent    {        public override void Do(object o)        {            var s = (string)o;        }    }    class Program    {        static void Main(string[] args)        {            CodeTimer.Initialize();            var iteration = 5000000;            Parent p1 = new Sub1();            Parent p2 = new Sub2();            CodeTimer.Time("Parse1", iteration, () =>            {                Parse1(p1, p2);            });            CodeTimer.Time("Parse2", iteration, () =>            {                Parse2();            });        }        static void Parse1(Parent p1, Parent p2)        {            object o1 = 23;            p1.Do(o1);            object o2 = "123456";            p2.Do(o2);        }        static void Parse2()        {            object o1 = 23;            if (o1 is int)            {                var s = (int)o1;            }            else if (o1 is string)            {                var s = (string)o1;            }            object o2 = "123456";            if (o2 is int)            {                var s = (int)o2;            }            else if (o2 is string)            {                var s = (string)o2;            }        }    }
潦草的代码

经过这两部分的优化,效率基本和Dapper持平了,但是hubro先生执意不放:“为什么我们代码基本一样,怎么没你的效率高呢?”,于是有了接下来的两个优化。

 

三、实例化对象


 Activator.CreateInstance给了我们一个很方便的实例化对象的方法。有Type参数和泛型参数的重载方法,如果我们可以用泛型,我们还可以加new()约束,用 T obj=new T()来实例化,但是经我测试,Activator.CreateInstance<T>()和 new T()的效率是一样的。为什么一样呢,用ILDASM查看一下IL代码,原来new T()生成的代码也是调用Activator.CreateInstance<T>()呀!!但是有一天hubro先生告诉了我一个更高效的方法:用表达式编译成强类型委托。

测试代码:

    class Program    {        static T NewT<T>() where T : new()        {            return new T();        }        static T CreateT<T>()        {            return Activator.CreateInstance<T>();        }        static void Main(string[] args)        {            CodeTimer.Initialize();            var iteration = 5000000;            var type = typeof(Person);            var newFun = Expression.Lambda<Func<Person>>(Expression.New(type)).Compile();            CodeTimer.Time("直接实例化", iteration, () =>            {                var p = new Person();            });            CodeTimer.Time("强类型委托", iteration, () =>            {                var p = newFun();            });            CodeTimer.Time("Activator非泛型", iteration, () =>            {                var p = Activator.CreateInstance(type);            });            CodeTimer.Time("Activator泛型", iteration, () =>            {                var p = CreateT<Person>();            });                        CodeTimer.Time("new T()", iteration, () =>            {                var p = NewT<Person>();            });        }    }

 

测试结果:

直接实例化        Time Elapsed:   51ms        CPU Cycles:     127,602,465        Gen 0:          50        Gen 1:          0        Gen 2:          0强类型委托        Time Elapsed:   135ms        CPU Cycles:     332,949,042        Gen 0:          50        Gen 1:          0        Gen 2:          0Activator非泛型        Time Elapsed:   401ms        CPU Cycles:     997,865,100        Gen 0:          50        Gen 1:          0        Gen 2:          0Activator泛型        Time Elapsed:   496ms        CPU Cycles:     1,238,255,824        Gen 0:          50        Gen 1:          0        Gen 2:          0new T()        Time Elapsed:   484ms        CPU Cycles:     1,207,260,662        Gen 0:          50        Gen 1:          0        Gen 2:          0请按任意键继续. . .

可以看出强类型委托实例化效率最接近直接实例化了,出乎意料的是非泛型的Activator.CreateInstance比泛型的还高一点,泛型版本和new T()前面说过了,是一样的。

 

四、类型转换


 MySql数据库类型对应到C#类型的时候,发现有两个不太确定,bit不对应bool,tinyint不对应byte,所以需要自己处理一下。把一个对象转换成值类型用几种方法测试一下:

测试代码:

        static void Main(string[] args)        {            CodeTimer.Initialize();            var iteration = 5000000;            var type = typeof(Person);            var newFun = Expression.Lambda<Func<Person>>(Expression.New(type)).Compile();            object obj = 23;            CodeTimer.Time("拆箱转换", iteration, () =>            {                var a = (int)obj;            });            CodeTimer.Time("Convert转换", iteration, () =>            {                var a = Convert.ToInt32(obj);            });            CodeTimer.Time("Parse转换", iteration, () =>            {                var a = int.Parse(obj.ToString());            });        }

测试结果:

拆箱转换        Time Elapsed:   28ms        CPU Cycles:     70,331,496        Gen 0:          0        Gen 1:          0        Gen 2:          0Convert转换        Time Elapsed:   68ms        CPU Cycles:     169,567,996        Gen 0:          0        Gen 1:          0        Gen 2:          0Parse转换        Time Elapsed:   1,141ms        CPU Cycles:     2,832,437,791        Gen 0:          63        Gen 1:          0        Gen 2:          0请按任意键继续. . .

所以在转换bool值的时候,可以这样

if (obj is bool){    var b = (bool)obj;}else{    var b = Convert.ToInt32(obj) > 0;}

经过这些优化,映射效率已经超过Dapper了。但是又有一天hubro先生给我截图,说他的Mapping已经超过我了,我一下子不淡定了,这怎么可能,我从来都是写一些简单的明显没有效率问题的代码,所以赶紧请教(就是下面要说的)!

 

五、DbDataReader的GetValue和GetValues


 在从DataReader取值的时候,hubro发现用GetValues一下子取出来,比在循环中一个一个取要快,测试了一下果真如此:

        public static List<T> ReaderToObjectList<T>(DbDataReader reader)        {            if (!reader.HasRows)            {                return new List<T>();            }            var refInfo = ReflectionHelper.GetInfo<T>();            List<T> list = new List<T>();            var first = true;            int length = reader.FieldCount;            ReflectionInfo<T>.Accessor[] accessorArray = new ReflectionInfo<T>.Accessor[length];            object[] values = new object[length];            while (reader.Read())            {                reader.GetValues(values);                T obj = refInfo.NewInstance();// Activator.CreateInstance<T>();                if (first)                {                    for (int i = 0; i < length; i++)                    {                        var fieldName = reader.GetName(i);                        var accessor = refInfo.GetAccessor(fieldName);                        accessorArray[i] = accessor;                        accessor.Set(obj, values[i]);                    }                    first = false;                }                else                {                    for (var i = 0; i < length; i++)                    {                        accessorArray[i].Set(obj, values[i]);                    }                }                list.Add(obj);            }            return list;        }

 至此,我们的效率已经甩开Dapper了!

一个大写的

Loogn.OrmLite映射优化记录