Method | Categories | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
---|---|---|---|---|---|---|---|
ManualSourceCodeMapping | Collection | 1,202.9 ns | 8.51 ns | 7.11 ns | 0.4387 | 0.0076 | 5.38 KB |
ManualSourceWithoutLinqCodeMapping | Collection | 861.3 ns | 16.93 ns | 16.63 ns | 0.4072 | 0.0067 | 5 KB |
AutoMapperMapping | Collection | 985.1 ns | 19.15 ns | 22.06 ns | 0.3777 | 0.0057 | 4.63 KB |
ManualSourceCodeMappingOneByOne | OneByOne | 1,202.2 ns | 17.03 ns | 15.93 ns | 0.4387 | 0.0076 | 5.38 KB |
ManualSourceWithoutLinqCodeMappingOneByOne | OneByOne | 751.7 ns | 7.02 ns | 5.48 ns | 0.3815 | 0.0067 | 4.68 KB |
AutoMapperMappingOneByOne | OneByOne | 1,389.4 ns | 22.70 ns | 17.72 ns | 0.3815 | 0.0057 | 4.68 KB |
Created
March 27, 2024 08:42
-
-
Save wdolek/cc07e6f1aa4c631757ca47801136c4c4 to your computer and use it in GitHub Desktop.
Proof that AutoMapper can be slower than your own manually written code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<OutputType>Exe</OutputType> | |
<TargetFramework>net8.0</TargetFramework> | |
<ImplicitUsings>enable</ImplicitUsings> | |
<Nullable>enable</Nullable> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="AutoMapper" Version="13.0.1" /> | |
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" /> | |
</ItemGroup> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace ConsoleApp154; | |
public class Parent | |
{ | |
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; | |
public string Name { get; set; } = "Bob"; | |
public List<Child> Children { get; set; } = []; | |
} | |
public class Child | |
{ | |
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; | |
public string Name { get; set; } = "Bob"; | |
} | |
public class ParentDto | |
{ | |
public DateTime CreatedUtc { get; set; } = DateTime.MinValue; | |
public string Name { get; set; } = ""; | |
public ChildDto[] Children { get; set; } = []; | |
} | |
public class ChildDto | |
{ | |
public DateTime CreatedUtc { get; set; } = DateTime.MinValue; | |
public string Name { get; set; } = ""; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Runtime.InteropServices; | |
using AutoMapper; | |
namespace ConsoleApp154; | |
public static class ManualMapperImpl | |
{ | |
public static ParentDto[] Map(IEnumerable<Parent> parents) => | |
parents.Select(Map).ToArray(); | |
public static ParentDto Map(Parent parent) => | |
new() | |
{ | |
CreatedUtc = parent.CreatedUtc, | |
Name = parent.Name, | |
Children = parent.Children | |
.Select( | |
x => | |
new ChildDto | |
{ | |
CreatedUtc = x.CreatedUtc, | |
Name = x.Name | |
} | |
) | |
.ToArray() | |
}; | |
} | |
public static class ManualMapperWithoutLinq | |
{ | |
public static ParentDto[] Map(IEnumerable<Parent> parents) | |
{ | |
var dtos = new List<ParentDto>(parents.Select(Map)); | |
return dtos.ToArray(); | |
} | |
public static ParentDto Map(Parent parent) | |
{ | |
var childrenDtos = new ChildDto[parent.Children.Count]; | |
var sourceSpan = CollectionsMarshal.AsSpan(parent.Children); | |
for (var i = 0; i < sourceSpan.Length; i++) | |
{ | |
childrenDtos[i] = new ChildDto | |
{ | |
CreatedUtc = sourceSpan[i].CreatedUtc, | |
Name = sourceSpan[i].Name | |
}; | |
} | |
return new ParentDto | |
{ | |
CreatedUtc = parent.CreatedUtc, | |
Name = parent.Name, | |
Children = childrenDtos, | |
}; | |
} | |
} | |
public static class AutoMapperImpl | |
{ | |
private static readonly IMapper Mapper = new MapperConfiguration(x => | |
{ | |
x.CreateMap<Parent, ParentDto>(); | |
x.CreateMap<Child, ChildDto>(); | |
}) | |
.CreateMapper(); | |
public static ParentDto[] Map(IEnumerable<Parent> parents) => Mapper.Map<ParentDto[]>(parents); | |
public static ParentDto Map(Parent parent) => Mapper.Map<ParentDto>(parent); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// based on: https://gist.github.com/mrpmorris/d48f881de623b47e5fc90b48a1875cb6 | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Running; | |
namespace ConsoleApp154; | |
public class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
BenchmarkRunner.Run<SimpleBenchmark>(); | |
} | |
} | |
[CategoriesColumn] | |
[MemoryDiagnoser] | |
public class SimpleBenchmark | |
{ | |
private static readonly Parent[] SourceData = CreateData(); | |
[Benchmark] | |
[BenchmarkCategory("OneByOne")] | |
public void ManualSourceCodeMappingOneByOne() | |
{ | |
ParentDto[] result = SourceData.Select(ManualMapperImpl.Map).ToArray(); | |
ValidateParents(result); | |
} | |
[Benchmark] | |
[BenchmarkCategory("OneByOne")] | |
public void ManualSourceWithoutLinqCodeMappingOneByOne() | |
{ | |
ParentDto[] result = SourceData.Select(ManualMapperWithoutLinq.Map).ToArray(); | |
ValidateParents(result); | |
} | |
[Benchmark] | |
[BenchmarkCategory("OneByOne")] | |
public void AutoMapperMappingOneByOne() | |
{ | |
ParentDto[] result = SourceData.Select(AutoMapperImpl.Map).ToArray(); | |
ValidateParents(result); | |
} | |
[Benchmark] | |
[BenchmarkCategory("Collection")] | |
public void ManualSourceCodeMapping() | |
{ | |
ParentDto[] result = ManualMapperImpl.Map(SourceData); | |
ValidateParents(result); | |
} | |
[Benchmark] | |
[BenchmarkCategory("Collection")] | |
public void ManualSourceWithoutLinqCodeMapping() | |
{ | |
ParentDto[] result = ManualMapperWithoutLinq.Map(SourceData); | |
ValidateParents(result); | |
} | |
[Benchmark] | |
[BenchmarkCategory("Collection")] | |
public void AutoMapperMapping() | |
{ | |
ParentDto[] result = AutoMapperImpl.Map(SourceData); | |
ValidateParents(result); | |
} | |
private static Parent[] CreateData() => | |
Enumerable.Range(1, 10).Select(x => | |
new Parent | |
{ | |
Children = Enumerable.Range(1, 10).Select(_ => new Child()).ToList() | |
}) | |
.ToArray(); | |
private static void ValidateParents(ParentDto[] parents) | |
{ | |
if (parents.Length != SourceData.Length) throw new Exception("Lengths are different"); | |
for (int parentIndex = 0; parentIndex < SourceData.Length; parentIndex++) | |
{ | |
Parent parent = SourceData[parentIndex]; | |
ParentDto parentDto = parents[parentIndex]; | |
ValidateParentData(parentIndex, parent, parentDto); | |
} | |
} | |
private static void ValidateParentData(int parentIndex, Parent parent, ParentDto parentDto) | |
{ | |
if (parentDto.CreatedUtc != parent.CreatedUtc) throw new Exception($"[{parentIndex}].CreatedUtc"); | |
if (parentDto.Name != parent.Name) throw new Exception($"[{parentIndex}].Name"); | |
if (parentDto.Children.Length != parent.Children.Count) throw new Exception($"[{parentIndex}].Children.Count"); | |
for (int childIndex = 0; childIndex < parent.Children.Count; childIndex++) | |
{ | |
Child child = parent.Children[childIndex]; | |
ChildDto childDto = parentDto.Children[childIndex]; | |
ValidateChildData(parentIndex, childIndex, child, childDto); | |
} | |
} | |
private static void ValidateChildData(int parentIndex, int childIndex, Child child, ChildDto childDto) | |
{ | |
if (childDto.CreatedUtc != child.CreatedUtc) throw new Exception($"[{parentIndex}, {childIndex}].CreatedUtc"); | |
if (childDto.Name != child.Name) throw new Exception($"[{parentIndex}, {childIndex}].Name"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment