Performance test conclusion: using new {} has the best performance, followed by Mapster and finally AutoMapper
Recently, when refactoring the code of a business interface, it was found that when using AutoMapper for object conversion, the response speed of the interface was very slow, more than 100 pieces of data, and the request time exceeded 8 seconds. In order to find out the reason, I tried to split the related queries and entity transformation of EF Core for analysis, and finally found that it was due to the performance bottleneck when using AutoMapper. So I tried to use the Linq method of select new {} to hard code conversion, and found that the efficiency was improved several times, which could be completed in basically 2 seconds. Out of curiosity, I tried to do a performance comparison test on AutoMapper.
testing procedure
testing environment
- 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
- Pressure measuring tool: benchmarkdotnet = v0 thirteen point one
Create project
dotnet new console -o ConsoleApp1
Install dependent packages
dotnet add package BenchmarkDotNet --version 0.13.1 dotnet add package AutoMapper --version 10.1.1 dotnet add package Mapster --version 7.2.0
Define the test Entity and DTO
public enum MyEnum { [Description("have in hand")] Doing, [Description("complete")] 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; } }
Configure the conversion relationship between Entity and DTO
AutoMapper configuration
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 configuration
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()); } }
Create performance test class
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); } }
Perform performance test
var summary = BenchmarkRunner.Run<PerformanceTest>();
dotnet run --project .\ConsoleApp1.csproj -c Release
Comparison of results
Pressure measurement comparison is carried out by using BenchmarkDotNet. From the above figure, we can see that the highest performance is achieved by using constructor (directly creating objects), then Mapster, and finally AutoMapper.
Use readableexpressions Visualizers viewing Execution Plan
AutoMapper has been used for object conversion in the project. Judging from the activity of Github, it is reasonable that such obvious performance problems should not occur. Curiosity drove me to conduct research. After communicating with the author, I learned that 'AutoMapper' itself will have a so-called execution plan, which can be implemented by installing plug-ins ReadableExpressions.Visualizers To see.
Add the following code in the configuration area of AutoMapper:
var executionPlan = configuration.BuildExecutionPlan(typeof(Entity), typeof(EntityDto)); var executionPlanStr = executionPlan.ToReadableString();
View the executionPlanStr value as follows:
(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; }; }
Performance tracking using dotTrace
Check the program execution by using JetBrains dotTrace, as shown in the following figure:
As can be seen from the figure, the GetDescription method takes up as much as 8.38% of the performance. I try to convert this enumeration to a string extension method, which returns a fixed value every time, as shown below:
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; } }
After the performance test again, it is found that the performance of AutoMapper has been improved a lot.
However, due to its large performance base, there are still performance problems