一次性能优化最佳实践

  上周五下班前,在Repository兄测试NLiteMapperEmitMapper文章中,发现了令我跌破眼镜的性能悬殊对比12283ms : 7ms

。真不可思议,与是便把EmitMapper的源代码和OOMapper 的源代码一起下载下来,以Release模式的方式做一个公平对比。测试代码

仍然沿用Repository兄的,代码如下:

public class SimpleClassFrom
        {
            public long ID { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
            public string Email { get; set; }
        }

        public class SimpleClassTo
        {
            public long ID { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
            public string Email { get; set; }
        }

        [Test]
        public void NLiteMapper_EmitMapper_Compare()
        {
            //consturct data
            List<SimpleClassFrom> fromCollection = new List<SimpleClassFrom>();
            int count = 10000;
            for (int i = 0; i < count; i++)
            {
                SimpleClassFrom from = new SimpleClassFrom
                {
                    ID = 123456,
                    Name = "test",
                    Age = 30,
                    Email = "hhhhhhhhhhhhhhh@hotmail.com"
                };
                fromCollection.Add(from);
            }


            //test nlite mapper
            var a = NLite.Mapper.CreateMapper<SimpleClassFrom, SimpleClassTo>();

            Stopwatch sw = Stopwatch.StartNew();
            sw.Start();
            for (int i = 0; i < count; i++)
            {
                a.Map(fromCollection[i]);
            }

            sw.Stop();

            Console.WriteLine("nlitemapper elapsed:{0}ms", sw.ElapsedMilliseconds);


            //test emit mapper
             var mapper = ObjectMapperManager.DefaultInstance
.GetMapper<SimpleClassFrom, SimpleClassTo>();



            sw = Stopwatch.StartNew();
            for (int i = 0; i < count; i++)
            {
                mapper.Map(fromCollection[i]);
            }
            sw.Stop();

            Console.WriteLine("emitmapper elapsed:{0}ms", sw.ElapsedMilliseconds);

            Console.Read();
        }

   然后进行了2次测试,取第二次的结果:nlitemapper elapsed:15475ms,emitmapper elapsed:1ms。这个结果还不如Repository兄的测试结果,但是也是意料之中。于是开始分析代码。EmitMapper是在Create 对象映射器的时候已经把对象映射的元数据通过Emit的方式进行了构建,而NLiteMapper创建对象映射器的时候并没有立即创建对象映射元数据,而是在第一次调用映射的时候开始创建,代码如下:

        Lazy<List<MemberMapping>> mappings;

        internal Lazy<List<MemberMapping>> Mappings
        {
            get
            {
                if (mappings != null)
                    return mappings;

                mappings = new Lazy<List<MemberMapping>>(() =>
                    {
                        var fromMembers = SourceMembers.Value;
                        var items = new List<MemberMapping>();

                        foreach (var toMember in DestinationMembers.Value)
                        {
                            string fromMemberPath = null;
                            MemberInfo fromMember = fromMembers.FirstOrDefault(m => IsMatchMember(m, toMember, ref fromMemberPath));
                            if (fromMember == null)
                                continue;

                            if (!string.IsNullOrEmpty(fromMemberPath))
                                fromMemberPath = fromMemberPath.Remove(0, 1);

                            items.Add(new MemberMapping
                            {
                                FromMemberPath = fromMemberPath,
                                From = GetMappingItem(fromMember, true),
                                To = GetMappingItem(toMember, false)
                            });
                        }

                        return items;
                    });

                return mappings;
            }
        }

于是为了公平性,先把Lazy创建元数据的地方都去掉。然后再进行测试:nlitemapper elapsed:15290ms,emitmapper elapsed:1ms,性能稍微提供了一丁点。

   进入NLiteMapper的ClassMapper的Map源代码:

        public override void Map(ref object from, ref object to)
        {
            var mappings = _Info.Mappings;
            var mappingCount = mappings.Count;

            if (_Info.memberMappings.Count == 0 && mappingCount == 0)
                return;

            if(to == null)
                to = ObjectCreator.Create(_Info.To);

            for(int i=0;i<mappingCount;i++)
            {
                
                var item = mappings[i];
                try
                {
                    object value = GetSourceMemberValue(ref from, ref item);//通过映射项元数据获取source 对象的一个属性值

                    var key = item.From.Type.FullName + "->" + item.To.Type.FullName;
                    if (_Info.CanUsingConverter(key))//是否自定义了转换器
                        value = _Info.converters[key].DynamicInvoke(value);
                    else //否则把Source对象的一个属性值转化为和Target对象的属性类型相匹配的值
                        value = Mapper.Map(value, item.From.Type, item.To.Type);

                    item.To.SetMember(to, value);// 设置Target对象的成员值
                }
                catch (Exception ex)
                {
                    State.AddError( string.Format("{0}.{1} -> {2}.{3}"
                        , item.From.Member.DeclaringType.Name
                        , item.From.Name
                        , item.To.Member.DeclaringType.Name
                        , item.To.Name), ex.Message);
                }
            }

            FilterMembers(ref from, ref to);
        }

  第一步:移除Try..Catch块的测试结果:nlitemapper elapsed:15489ms,emitmapper elapsed:1ms,几乎没有影响,于是恢复Try...Catch块。

     第二步:定位核心代码

                   object value = GetSourceMemberValue(ref from, ref item);//通过映射项元数据获取source 对象的一个属性值

                    var key = item.From.Type.FullName + "->" + item.To.Type.FullName;
                    if (_Info.CanUsingConverter(key))//是否自定义了转换器
                        value = _Info.converters[key].DynamicInvoke(value);
                    else //否则把Source对象的一个属性值转化为和Target对象的属性类型相匹配的值
                        value = Mapper.Map(value, item.From.Type, item.To.Type);

                    item.To.SetMember(to, value);// 设置Target对象的成员值

   注释掉核心代码,并做测试:nlitemapper elapsed:39ms,emitmapper elapsed:1ms。测试结果相当激动人心,说明性能的
关键代码段就在上面那几句代码中,于是恢复掉注释,继续研究。

   第三步:一眼找出这句代码的问题 var key = item.From.Type.FullName + "->" + item.To.Type.FullName,这个Key的获取应

该可以被缓存起来保存到元数据中去,不应该每次都重新计算,于是修改代码并测试:nlitemapper elapsed:15462ms,emitmapper

elapsed:1ms,几乎没有影响

   第四步:定位到item.To.SetMember 源代码:

   [DebuggerDisplay("{Name}")]
    struct MappingItem
    {
        public Type Type;
        public MemberInfo Member;

        private Setter setMember;
        public Setter SetMember
        {
            get
            {
                if (setMember == null)
                    setMember = Member.GetSetter();
                return setMember;
            }
        }

        private Getter getMember;
        public Getter GetMember
        {
            get
            {
                if (getMember == null)
                    getMember = Member.GetGetter();
                return getMember;
            }
        }

        public string Name
        {
            get
            {
                return string.Format("[{0} {1}]", Type.FullName, Member.Name);
            }
        }

        public override string ToString()
        {
            return Name;
        }
    }

    Setter/Getter Delegate( 通过Emit创建的)是Lazy创建的,把它也改为立即创建为了和EmitMapper更公平比较,于是进行相关改造,并测试:nlitemapper elapsed:2011ms,emitmapper elapsed:1ms。 性能一下子从15000ms多一下缩减到2000ms多一点,等于性能提升了7倍,但是离39ms 还差很多。

      第五步:分析 这句 代码 object value = GetSourceMemberValue(ref from, ref item); 获取 发现两个参数都用了关键字ref, 第一个参数是source 对象,不管它,第二个参数item 的类型是一个结构MemberMapping,所以为了提升性能就用了 ref 关键字来减少调用函数栈的开销。随即又想,我直接用Class就可以不用ref关键子了,于是改成Class类型顺手测试了一下,结果非常吃惊:nlitemapper elapsed:763ms,emitmapper elapsed:3ms。关于ref struct 体参数变量和Class参数变量的性能更详细的测试,准备以后再详细研究。

     第六步: 把最后一句代码属性赋值代码注释掉:item.To.SetMember(to, value);看看测试结果:nlitemapper elapsed:773ms,emitmapper elapsed:1ms 结果也在意料之中,NLiteMapper的 Emit也不是盖的,

几乎没有任何反射开销,呵呵。

第七部: 根据测试路径,性能瓶颈应该是value = Mapper.Map(value, item.From.Type, item.To.Type) 这句代码了,把这句代码注释掉

,看看具体的验证结果:nlitemapper elapsed:42ms,emitmapper elapsed:1ms,果不其然就是这里。这句格式化属性值的代码,是否真的有

必要格式化?仔细想了想,在大部分属性映射中,属性类型都是一样的,不需要格式化,这符合典型的2:8原则,这里为了代码的一致性而忽视了2:8

原则,这是很多爱洁癖程序员(爱代码重构的程序员)容易犯的错误,随后加了一句简单的ifelse判断。性能测试结果如预期的一样:nlitemapper elapsed:42ms,emitmapper elapsed:1ms。

最后又优化了其它的几个小方面代码性能提升到:20ms左右,这个结果和emitMapper 的结果相差不多了,但是我会继续优化使之差距更小,当

然NLiteMapper中有很多其它的Mapper,也需要进行详细的性能检测,过一段时间再发布一个版本。

总结:

1. 函数参数中尽量不要用Struct参数或者Ref Struct参数

2. 在代码整洁和简洁化的同时,不要忘记了2:8原则

最后附加上AutoMapper,NLiteMapper,EmitMapper的测试结果:

nlitemapper elapsed:18ms
emitmapper elapsed:1ms
autoMapper elapsed:842ms

posted @ 2011-04-11 11:28 风云 阅读(...) 评论(...) 编辑 收藏