• 放弃 AutoMapper ,拥抱 Mapster


    性能测试结论:使用 new {} 的方式性能最佳,其次是 Mapster ,最后是 AutoMapper

    最近在对一个业务接口做代码重构时,发现在使用 AutoMapper 做对象转换时,接口响应速度极慢,100多条数据,请求时长都超过了8秒。为了找出原因所在,我尝试将 EF Core 的相关查询和 实体转换拆分开来做分析,最终发现是由于使用 AutoMapper 时,性能出现了瓶颈。于是我尝试使用 select new {} 的 Linq 方式来硬编码转换,发现效率提升了几倍,基本 2秒内就能完成。出于好奇,我尝试对 AutoMapper 做一下性能对比测试。

    测试步骤

    测试环境

    • OS:Windows 10.0.19042.1348 (20H2/October2020Update)
    • CPU:Intel Core i5-7500 CPU 3.40GHz (Kaby Lake), 1 CPU, 4 logical and 4 physical cores
    • SDK:NET SDK=6.0.100
    • 压测工具:BenchmarkDotNet=v0.13.1

    创建项目

    dotnet new console -o ConsoleApp1
    

    安装依赖包

    dotnet add package BenchmarkDotNet --version 0.13.1
    dotnet add package AutoMapper --version 10.1.1
    dotnet add package Mapster --version 7.2.0
    

    定义用于测试 Entity 和 DTO

    public enum MyEnum
    {
        [Description("进行中")]
        Doing,
        [Description("完成")]
        Done
    }
    public class Entity
    {
        public int Id { get; set; }
        public Guid Oid { get; set; }
        public string? NickName { get; set; }
        public bool Created { get; set; }
        public MyEnum State { get; set; }
    }
    
    public class EntityDto
    {
        public int Id { get; set; }
        public Guid Oid { get; set; }
        public string? NickName { get; set; }
        public bool Created { get; set; }
        public MyEnum Enum { get; set; }
        public string? EnumString { get; set; }
    }
    

    配置 Entity 和 DTO 之间的转换关系

    AutoMapper 配置

      public class AutoMapperProfile : Profile
      {
          public AutoMapperProfile()
          {
              CreateMap<Entity, EntityDto>()
                   .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
                   .ForMember(dest => dest.Oid, opt => opt.MapFrom(src => src.Oid))
                   .ForMember(dest => dest.NickName, opt => opt.MapFrom(src => src.NickName))
                   .ForMember(dest => dest.Created, opt => opt.MapFrom(src => src.Created))
                   .ForMember(dest => dest.Enum, opt => opt.MapFrom(src => src.State))
                   .ForMember(dest => dest.EnumString, opt => opt.MapFrom(src => src.State.GetDescription()));
          }
      }
    

    Mapster 配置

    public class MapsterProfile : TypeAdapterConfig
    {
        public MapsterProfile()
        {
            ForType<Entity, EntityDto>()
                 .Map(dest => dest.Id, src => src.Id)
                 .Map(dest => dest.Oid, src => src.Oid)
                 .Map(dest => dest.NickName, src => src.NickName)
                 .Map(dest => dest.Created, src => src.Created)
                 .Map(dest => dest.Enum, src => src.State)
                 .Map(dest => dest.EnumString, src => src.State.GetDescription());
        }
    }
    

    创建性能测试类

    public class PerformanceTest
    {
        private IReadOnlyList<entity> _entities;
        private readonly AutoMapper.IMapper _autoMapper;
        private readonly Mapper _mapsterMapper;
    
        public PerformanceTest()
        {
            var mocker = new AutoMocker();
            _entities = Enumerable.Range(0, 1000000).Select(x => mocker.CreateInstance<entity>()).ToList();
    
            var configuration = new AutoMapper.MapperConfiguration(cfg => cfg.AddProfile<AutoMapperProfile>());
             _autoMapper = configuration.CreateMapper();
             _mapsterMapper = new MapsterMapper.Mapper(new MapsterProfile());
        }
    
        [Benchmark]
        public void Constructor()
        {
            var dtos = _entities.Select(x => new EntityDto
            {
                Id = x.Id,
                Oid = x.Oid,
                NickName = x.NickName,
                Created = x.Created,
                Enum = x.State,
                EnumString = x.State.GetDescription(),
            });
        }
    
        [Benchmark]
        public void AutoMapper()
        {
            var dtos = _autoMapper.Map<ienumerable<entitydto>>(_entities);
    
        }
        [Benchmark]
        public void Mapster()
        {
            var dtos = _mapsterMapper.Map<ienumerable<entitydto>>(_entities);
        }
    }
    

    执行性能测试

    var summary = BenchmarkRunner.Run<PerformanceTest>();
    
    dotnet run --project .\ConsoleApp1.csproj -c Release
    

    结果对比

    通过使用 BenchmarkDotNet 来进行压测对比。从上图我们可以看出,使用 Constructor(直接创建对象) 的方式性能是最高的,然后就是 Mapster,最后才是 AutoMapper

    使用 ReadableExpressions.Visualizers 查看 Execution Plan

    在项目中一直在使用 AutoMapper 来做对象转换,看 Github 活跃度,按理说不应该出现这么明显的性能问题。好奇心驱使我项研究一下,通过和作者沟通后了解到,'AutoMapper' 本身会有一个所谓的执行计划 execution plan,可以通过安装插件 ReadableExpressions.Visualizers 来查看。

    在 AutoMapper 的配置地方添加如下代码:

    var executionPlan = configuration.BuildExecutionPlan(typeof(Entity), typeof(EntityDto));
    var executionPlanStr = executionPlan.ToReadableString();
    

    查看 executionPlanStr 值,如下所示:

    (src, dest, ctxt) =>
    {
        EntityDto typeMapDestination;
        return (src == null)
            ? null
            : {
                typeMapDestination = dest ?? new EntityDto();
                try
                {
                    var resolvedValue =
                    {
                        try
                        {
                            Entity src;
                            return (((src = src) == null) || false) ? default(int) : src.Id;
                        }
                        catch (NullReferenceException)
                        {
                            return default(int);
                        }
                        catch (ArgumentNullException)
                        {
                            return default(int);
                        }
                    };
    
                    typeMapDestination.Id = resolvedValue;
                }
                catch (Exception ex)
                {
                    return throw new AutoMapperMappingException(
                        "Error mapping types.",
                        ex,
                        AutoMapper.TypePair,
                        TypeMap,
                        PropertyMap);
                }
                try
                {
                    var resolvedValue =
                    {
                        try
                        {
                            Entity src;
                            return (((src = src) == null) || false) ? default(Guid) : src.Oid;
                        }
                        catch (NullReferenceException)
                        {
                            return default(Guid);
                        }
                        catch (ArgumentNullException)
                        {
                            return default(Guid);
                        }
                    };
    
                    typeMapDestination.Oid = resolvedValue;
                }
                catch (Exception ex)
                {
                    return throw new AutoMapperMappingException(
                        "Error mapping types.",
                        ex,
                        AutoMapper.TypePair,
                        TypeMap,
                        PropertyMap);
                }
                try
                {
                    var resolvedValue =
                    {
                        try
                        {
                            Entity src;
                            return (((src = src) == null) || false) ? null : src.NickName;
                        }
                        catch (NullReferenceException)
                        {
                            return null;
                        }
                        catch (ArgumentNullException)
                        {
                            return null;
                        }
                    };
    
                    var propertyValue = (resolvedValue == null) ? null : resolvedValue;
                    typeMapDestination.NickName = propertyValue;
                }
                catch (Exception ex)
                {
                    return throw new AutoMapperMappingException(
                        "Error mapping types.",
                        ex,
                        AutoMapper.TypePair,
                        TypeMap,
                        PropertyMap);
                }
                try
                {
                    var resolvedValue =
                    {
                        try
                        {
                            Entity src;
                            return (((src = src) == null) || false) ? default(bool) : src.Created;
                        }
                        catch (NullReferenceException)
                        {
                            return default(bool);
                        }
                        catch (ArgumentNullException)
                        {
                            return default(bool);
                        }
                    };
    
                    typeMapDestination.Created = resolvedValue;
                }
                catch (Exception ex)
                {
                    return throw new AutoMapperMappingException(
                        "Error mapping types.",
                        ex,
                        AutoMapper.TypePair,
                        TypeMap,
                        PropertyMap);
                }
                try
                {
                    var resolvedValue =
                    {
                        try
                        {
                            Entity src;
                            return (((src = src) == null) || false) ? default(MyEnum) : src.State;
                        }
                        catch (NullReferenceException)
                        {
                            return default(MyEnum);
                        }
                        catch (ArgumentNullException)
                        {
                            return default(MyEnum);
                        }
                    };
    
                    typeMapDestination.Enum = resolvedValue;
                }
                catch (Exception ex)
                {
                    return throw new AutoMapperMappingException(
                        "Error mapping types.",
                        ex,
                        AutoMapper.TypePair,
                        TypeMap,
                        PropertyMap);
                }
                try
                {
                    var resolvedValue =
                    {
                        try
                        {
                            return ((Enum)src.State).GetDescription();
                        }
                        catch (NullReferenceException)
                        {
                            return null;
                        }
                        catch (ArgumentNullException)
                        {
                            return null;
                        }
                    };
    
                    var propertyValue = (resolvedValue == null) ? null : resolvedValue;
                    typeMapDestination.EnumString = propertyValue;
                }
                catch (Exception ex)
                {
                    return throw new AutoMapperMappingException(
                        "Error mapping types.",
                        ex,
                        AutoMapper.TypePair,
                        TypeMap,
                        PropertyMap);
                }
    
                return typeMapDestination;
            };
    }
    

    使用 dotTrace 进行性能跟踪

    通过使用 JetBrains dotTrace 来查看程序执行情况,如下图所示:

    从图中我们可以看到,GetDescription 方法性能占用高达 8.38%。我尝试将这个枚举转字符串的扩展方法每次都返回固定值,如下所示:

    public static class Extension
    {
        public static string GetDescription(this Enum value)
        {
            return "aaa";
            //var field = value.GetType().GetField(value.ToString());
            //if (field == null) return "";
            //return Attribute.GetCustomAttribute(field,
            //    typeof(DescriptionAttribute)) is not DescriptionAttribute attribute
            //    ? value.ToString()
            //    : attribute.Description;
        }
    }
    
    

    再次进行性能测试,发现 AutoMapper 的性能提升了不少。

    但是,由于本身性能基数比较大,所有依然存在性能问题

    相关参考

  • 相关阅读:
    DEV勾选框按钮呈现
    C#事务
    C#调用python脚本
    centos 磁盘满
    PostgreSQL库表字段信息
    Nginx Configuration for windows
    .NET Core 6.0之读取配置文件
    WinUI迁移到即将"过时"的.NET MAUI个人体验
    客户案例Husqvarna AB
    客户案例SES S.A.
  • 原文地址:https://www.cnblogs.com/hippieZhou/p/15590457.html
Copyright © 2020-2023  润新知