Skip to content

Instantly share code, notes, and snippets.

@wdolek
Created March 27, 2024 08:42
Show Gist options
  • Save wdolek/cc07e6f1aa4c631757ca47801136c4c4 to your computer and use it in GitHub Desktop.
Save wdolek/cc07e6f1aa4c631757ca47801136c4c4 to your computer and use it in GitHub Desktop.
Proof that AutoMapper can be slower than your own manually written code
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
<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>
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; } = "";
}
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);
}
// 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