Skip to content

Instantly share code, notes, and snippets.

@mrpmorris
Last active April 11, 2024 21:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mrpmorris/d48f881de623b47e5fc90b48a1875cb6 to your computer and use it in GitHub Desktop.
Save mrpmorris/d48f881de623b47e5fc90b48a1875cb6 to your computer and use it in GitHub Desktop.
Proof that automapper can be faster than your own manually written code
Method Mean Error StdDev Gen0 Gen1 Allocated
ManualSourceCodeMapping 1.542 us 0.0206 us 0.0183 us 0.4387 0.0076 5.38 KB
AutoMapperMapping 1.199 us 0.0221 us 0.0288 us 0.3777 0.0057 4.63 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 AutoMapper;
namespace ConsoleApp154;
public static class ManualMapperImpl
{
public static ParentDto[] Map(IEnumerable<Parent> parents) =>
parents
.Select(x =>
new ParentDto
{
CreatedUtc = x.CreatedUtc,
Name = x.Name,
Children = x.Children
.Select(x =>
new ChildDto
{
CreatedUtc = x.CreatedUtc,
Name = x.Name
}
)
.ToArray()
}
)
.ToArray();
}
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);
}
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace ConsoleApp154;
public class Program
{
public static void Main(string[] args)
{
BenchmarkRunner.Run<SimpleBenchmark>();
}
}
public class SimpleBenchmark
{
private static readonly Parent[] SourceData = CreateData();
[Benchmark]
public void ManualSourceCodeMapping()
{
ParentDto[] result = ManualMapperImpl.Map(SourceData);
ValidateParents(result);
}
[Benchmark]
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");
}
}
@onionhammer
Copy link

onionhammer commented Apr 11, 2024

cough

using AutoMapper;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Running;
using Riok.Mapperly.Abstractions;

var summary = BenchmarkRunner.Run<BenchMappers>();

[SimpleJob(RunStrategy.ColdStart, iterationCount: 5)]
[MemoryDiagnoser]
public class BenchMappers
{
    private RiokMapper mapperlyMapper = null!;
    private IMapper autoMapper = null!;
    private List<Source> sourceData = null!;

    [GlobalSetup]
    public void GlobalSetup()
    {
        // Setup mapperly
        this.mapperlyMapper = new RiokMapper();

        // Setup automapper
        var config = new MapperConfiguration(cfg => cfg.CreateMap<Source, Destination>());

        this.autoMapper = config.CreateMapper();

        // Setup data
        this.sourceData = Enumerable.Range(0, 1000).Select(i => new Source
        {
            Id = i,
            Name = $"Name {i}",
            Description = $"Description {i}",
            CreatedAt = DateTime.Now,
            Children = Enumerable.Range(0, 10).Select(j => new Source
            {
                Id = j,
                Name = $"Name {j}",
                Description = $"Description {j}",
                CreatedAt = DateTime.Now
            }).ToList()
        }).ToList();
    }

    [Benchmark(Baseline = true)]
    public void MapManually()
    {
        foreach (var source in this.sourceData)
        {
            var destination = MapSource(source);
        }

        static Destination MapSource(Source source)
        {
            return new Destination
            {
                Id = source.Id,
                Name = source.Name,
                Description = source.Description,
                CreatedAt = source.CreatedAt,
                Children = source.Children?.Select(child => new Destination
                {
                    Id = child.Id,
                    Name = child.Name,
                    Description = child.Description,
                    CreatedAt = child.CreatedAt
                }).ToList()
            };
        }
    }

    [Benchmark]
    public void MapWithAutoMapper()
    {
        foreach (var source in this.sourceData)
        {
            var destination = this.autoMapper.Map<Destination>(source);
        }
    }

    [Benchmark]
    public void MapWithMapperly()
    {
        foreach (var source in this.sourceData)
        {
            var destination = this.mapperlyMapper.Map(source);
        }
    }
}

public class Source
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public required string Description { get; set; }
    public DateTime CreatedAt { get; set; }

    public List<Source>? Children { get; set; }
}

public class Destination
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public required string Description { get; set; }
    public DateTime CreatedAt { get; set; }

    public List<Destination>? Children { get; set; }
}

[Mapper]
public partial class RiokMapper
{
    public partial Destination Map(Source source);
}
Method Mean Error StdDev Median Ratio RatioSD Allocated Alloc Ratio
MapManually 371.9 us 1,083.0 us 281.3 us 243.1 us 1.00 0.00 805.08 KB 1.00
MapWithAutoMapper 4,482.7 us 22,641.7 us 5,880.0 us 1,812.4 us 9.41 4.45 2461.33 KB 3.06
MapWithMapperly 625.3 us 1,061.9 us 275.8 us 471.8 us 1.89 0.40 773.83 KB 0.96

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment