zoukankan      html  css  js  c++  java
  • 一次性能优化最佳实践

      上周五下班前,在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
    

  • 相关阅读:
    [概率论]2017.5.9
    [概率论] 2017 5.2
    [离散数学II]2017.5.2
    [离散数学II]2017.4.25
    [概率论]2017.4.19
    [概率论] 2017.4.18
    [离散数学II]2017.4.18
    [离散数学II]2017.4.11
    [概率论]2017.4.12
    [概率论]2017.4.5
  • 原文地址:https://www.cnblogs.com/netcasewqs/p/2012249.html
Copyright © 2011-2022 走看看