欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一次性能优化最佳实践

程序员文章站 2022-03-02 17:08:49
...

  上周五下班前,在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 = "[email protected]"
                };
                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

转载于:https://www.cnblogs.com/netcasewqs/archive/2011/04/11/2012249.html