ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

放弃 AutoMapper ,拥抱 Mapster

2022-01-03 19:33:54  阅读:220  来源: 互联网

标签:src return dest catch AutoMapper Mapster 拥抱 public


性能测试结论:使用 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 的性能提升了不少。

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

相关参考

标签:src,return,dest,catch,AutoMapper,Mapster,拥抱,public
来源: https://www.cnblogs.com/hippieZhou/p/15590457.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有