From e35dc294d5b3867bfc90cf64bebcd32d833fa522 Mon Sep 17 00:00:00 2001 From: Qyperion Date: Tue, 12 May 2026 17:23:42 +0300 Subject: [PATCH 1/5] feat(benchmark): fix and actualize Benchmark project --- src/.editorconfig | 6 +- .../Benchmark.Development.csproj | 38 ++-- .../Benchmarks/Config.cs | 8 +- .../Benchmarks/TestAll.cs | 6 +- .../Benchmarks/TestComplexTypes.cs | 8 +- .../Benchmarks/TestSimpleTypes.cs | 8 +- src/Benchmark.Development/Classes/Customer.cs | 4 +- src/Benchmark.Development/Classes/Foo.cs | 5 +- src/Benchmark.Development/MapsterVersion.cs | 4 +- src/Benchmark.Development/Program.cs | 4 +- src/Benchmark.Development/TestAdaptHelper.cs | 15 +- src/Benchmark/Benchmark.csproj | 97 +++++----- src/Benchmark/Benchmarks/Config.cs | 4 +- .../Benchmarks/MappingBenchmarkBase.cs | 12 ++ src/Benchmark/Benchmarks/TestAll.cs | 108 ++++++----- src/Benchmark/Benchmarks/TestComplexTypes.cs | 82 +++++---- src/Benchmark/Benchmarks/TestSimpleTypes.cs | 81 +++++---- src/Benchmark/Classes/Customer.cs | 4 +- src/Benchmark/Classes/Foo.cs | 5 +- src/Benchmark/Comparisons/FacetModels.cs | 22 +++ src/Benchmark/Comparisons/MapperlyModels.cs | 62 +++++++ src/Benchmark/CustomerMapper.g.cs | 171 +++++++++--------- src/Benchmark/CustomerMapper.tt | 4 +- src/Benchmark/FooMapper.g.cs | 131 +++++++------- src/Benchmark/FooMapper.tt | 4 +- src/Benchmark/Program.cs | 6 +- src/Benchmark/TestAdaptHelper.cs | 146 +++++++++------ 27 files changed, 605 insertions(+), 440 deletions(-) create mode 100644 src/Benchmark/Benchmarks/MappingBenchmarkBase.cs create mode 100644 src/Benchmark/Comparisons/FacetModels.cs create mode 100644 src/Benchmark/Comparisons/MapperlyModels.cs diff --git a/src/.editorconfig b/src/.editorconfig index 6600eeea..707ccf5f 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -1,4 +1,8 @@ -[*.cs] +[*.{csproj,props}] +indent_style = space +indent_size = 2 + +[*.cs] # S3220: Method calls should not resolve ambiguously to overloads with "params" dotnet_diagnostic.S3220.severity = suggestion diff --git a/src/Benchmark.Development/Benchmark.Development.csproj b/src/Benchmark.Development/Benchmark.Development.csproj index 2213d7a4..5b945fcf 100644 --- a/src/Benchmark.Development/Benchmark.Development.csproj +++ b/src/Benchmark.Development/Benchmark.Development.csproj @@ -3,14 +3,18 @@ Exe net10.0 - true - enable + 12.0 + enable - True + enable + Mapster.Benchmark.Development + + true Benchmark.Development.snk + True False - 7.4.0 - 12.0 + + 7.4.0 @@ -21,17 +25,17 @@ - - - - - - - - - - - + + + + + + + + - + + + + diff --git a/src/Benchmark.Development/Benchmarks/Config.cs b/src/Benchmark.Development/Benchmarks/Config.cs index 87dad0fe..9dfcf38b 100644 --- a/src/Benchmark.Development/Benchmarks/Config.cs +++ b/src/Benchmark.Development/Benchmarks/Config.cs @@ -1,14 +1,12 @@ -using Benchmark.Development; -using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Exporters.Csv; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; -using Perfolizer.Models; -namespace Benchmark.Benchmarks +namespace Mapster.Benchmark.Development.Benchmarks { public class Config : ManualConfig { @@ -32,7 +30,7 @@ public Config() AddColumn(BaselineRatioColumn.RatioMean); AddColumnProvider(DefaultColumnProviders.Metrics); - + foreach (var version in MapsterVersion.Get()) diff --git a/src/Benchmark.Development/Benchmarks/TestAll.cs b/src/Benchmark.Development/Benchmarks/TestAll.cs index b3e28a76..5d0bac95 100644 --- a/src/Benchmark.Development/Benchmarks/TestAll.cs +++ b/src/Benchmark.Development/Benchmarks/TestAll.cs @@ -1,7 +1,7 @@ -using Benchmark.Classes; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Development.Classes; -namespace Benchmark.Benchmarks +namespace Mapster.Benchmark.Development.Benchmarks { public class TestAll { diff --git a/src/Benchmark.Development/Benchmarks/TestComplexTypes.cs b/src/Benchmark.Development/Benchmarks/TestComplexTypes.cs index 62d7a764..df4b3beb 100644 --- a/src/Benchmark.Development/Benchmarks/TestComplexTypes.cs +++ b/src/Benchmark.Development/Benchmarks/TestComplexTypes.cs @@ -1,7 +1,7 @@ -using Benchmark.Classes; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Development.Classes; -namespace Benchmark.Benchmarks +namespace Mapster.Benchmark.Development.Benchmarks { public class TestComplexTypes { @@ -15,7 +15,7 @@ public void MapsterTest() { TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); } - + [GlobalSetup(Target = nameof(MapsterTest))] public void SetupMapster() { diff --git a/src/Benchmark.Development/Benchmarks/TestSimpleTypes.cs b/src/Benchmark.Development/Benchmarks/TestSimpleTypes.cs index 8678a8ec..55aa0967 100644 --- a/src/Benchmark.Development/Benchmarks/TestSimpleTypes.cs +++ b/src/Benchmark.Development/Benchmarks/TestSimpleTypes.cs @@ -1,7 +1,7 @@ -using Benchmark.Classes; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Development.Classes; -namespace Benchmark.Benchmarks +namespace Mapster.Benchmark.Development.Benchmarks { public class TestSimpleTypes { @@ -15,7 +15,7 @@ public void MapsterTest() { TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); } - + [GlobalSetup(Target = nameof(MapsterTest))] public void SetupMapster() { diff --git a/src/Benchmark.Development/Classes/Customer.cs b/src/Benchmark.Development/Classes/Customer.cs index 5fac9cef..694d0b03 100644 --- a/src/Benchmark.Development/Classes/Customer.cs +++ b/src/Benchmark.Development/Classes/Customer.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Benchmark.Classes +namespace Mapster.Benchmark.Development.Classes { public class Address { diff --git a/src/Benchmark.Development/Classes/Foo.cs b/src/Benchmark.Development/Classes/Foo.cs index 063541b8..5e3fa21e 100644 --- a/src/Benchmark.Development/Classes/Foo.cs +++ b/src/Benchmark.Development/Classes/Foo.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace Benchmark.Classes +namespace Mapster.Benchmark.Development.Classes { public class Foo { diff --git a/src/Benchmark.Development/MapsterVersion.cs b/src/Benchmark.Development/MapsterVersion.cs index bfca6a69..391f06d5 100644 --- a/src/Benchmark.Development/MapsterVersion.cs +++ b/src/Benchmark.Development/MapsterVersion.cs @@ -1,4 +1,4 @@ -namespace Benchmark.Development +namespace Mapster.Benchmark.Development { internal static class MapsterVersion { @@ -6,7 +6,7 @@ internal static class MapsterVersion internal static string[] Get() => [ "7.4.0", - "9.0.0-pre01" + "10.0.0" ]; } } diff --git a/src/Benchmark.Development/Program.cs b/src/Benchmark.Development/Program.cs index 32e641ed..4e560383 100644 --- a/src/Benchmark.Development/Program.cs +++ b/src/Benchmark.Development/Program.cs @@ -1,5 +1,5 @@ -using Benchmark.Benchmarks; -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Running; +using Mapster.Benchmark.Development.Benchmarks; var switcher = new BenchmarkSwitcher(new[] { diff --git a/src/Benchmark.Development/TestAdaptHelper.cs b/src/Benchmark.Development/TestAdaptHelper.cs index c6e06739..9e92fa43 100644 --- a/src/Benchmark.Development/TestAdaptHelper.cs +++ b/src/Benchmark.Development/TestAdaptHelper.cs @@ -1,12 +1,11 @@ -using Benchmark.Classes; -using Mapster; +using Mapster.Benchmark.Development.Classes; using System.Linq.Expressions; -namespace Benchmark +namespace Mapster.Benchmark.Development { public static class TestAdaptHelper { - + public static Customer SetupCustomerInstance() { return new Customer @@ -64,8 +63,8 @@ private static void SetupCompiler(MapsterCompilerType type) TypeAdapterConfig.GlobalSettings.Compiler = type switch { MapsterCompilerType.Default => _defaultCompiler, - // MapsterCompilerType.Roslyn => exp => exp.CompileWithDebugInfo(), - // MapsterCompilerType.FEC => exp => exp.CompileFast(), + // MapsterCompilerType.Roslyn => exp => exp.CompileWithDebugInfo(), + // MapsterCompilerType.FEC => exp => exp.CompileFast(), _ => throw new ArgumentOutOfRangeException(nameof(type)), }; } @@ -75,14 +74,14 @@ public static void ConfigureMapster(Foo fooInstance, MapsterCompilerType type) TypeAdapterConfig.GlobalSettings.Compile(typeof(Foo), typeof(Foo)); //recompile fooInstance.Adapt(); //exercise } - + public static void ConfigureMapster(Customer customerInstance, MapsterCompilerType type) { SetupCompiler(type); TypeAdapterConfig.GlobalSettings.Compile(typeof(Customer), typeof(CustomerDTO)); //recompile customerInstance.Adapt(); //exercise } - + public static void TestMapsterAdapter(TSrc item, int iterations) where TSrc : class where TDest : class, new() diff --git a/src/Benchmark/Benchmark.csproj b/src/Benchmark/Benchmark.csproj index b6872ab8..451136bd 100644 --- a/src/Benchmark/Benchmark.csproj +++ b/src/Benchmark/Benchmark.csproj @@ -1,45 +1,54 @@ - - - - Exe - net9.0 - true - **/*.g.cs - - - - True - True - CustomerMapper.tt - - - True - True - FooMapper.tt - - - - - - - - - - - - - - - - TextTemplatingFileGenerator - CustomerMapper.g.cs - - - TextTemplatingFileGenerator - FooMapper.g.cs - - - - - + + + Exe + net10.0 + + Mapster.Benchmark + enable + + true + **/*.g.cs + + + + + + + + + + + + + + + + + + + True + True + CustomerMapper.tt + + + True + True + FooMapper.tt + + + + + + TextTemplatingFileGenerator + CustomerMapper.g.cs + + + TextTemplatingFileGenerator + FooMapper.g.cs + + + + + + \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/Config.cs b/src/Benchmark/Benchmarks/Config.cs index 5779b353..fb45726c 100644 --- a/src/Benchmark/Benchmarks/Config.cs +++ b/src/Benchmark/Benchmarks/Config.cs @@ -6,7 +6,7 @@ using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; -namespace Benchmark.Benchmarks +namespace Mapster.Benchmark.Benchmarks { public class Config : ManualConfig { @@ -20,12 +20,14 @@ public Config() AddDiagnoser(MemoryDiagnoser.Default); AddColumn(TargetMethodColumn.Method); + AddColumnProvider(DefaultColumnProviders.Params); AddColumn(StatisticColumn.Mean); AddColumn(StatisticColumn.StdDev); AddColumn(StatisticColumn.Error); AddColumn(BaselineRatioColumn.RatioMean); + AddColumn(BaselineAllocationRatioColumn.RatioMean); AddColumnProvider(DefaultColumnProviders.Metrics); AddJob(Job.ShortRun diff --git a/src/Benchmark/Benchmarks/MappingBenchmarkBase.cs b/src/Benchmark/Benchmarks/MappingBenchmarkBase.cs new file mode 100644 index 00000000..e5f5ab77 --- /dev/null +++ b/src/Benchmark/Benchmarks/MappingBenchmarkBase.cs @@ -0,0 +1,12 @@ +using BenchmarkDotNet.Attributes; + +namespace Mapster.Benchmark.Benchmarks +{ + public abstract class MappingBenchmarkBase + { + public IEnumerable MapOperationValues => new[] { 1_000, 10_000, 100_000, 1_000_000 }; + + [ParamsSource(nameof(MapOperationValues))] + public int MapOperations { get; set; } + } +} \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestAll.cs b/src/Benchmark/Benchmarks/TestAll.cs index 17581ddf..f2ff5e0a 100644 --- a/src/Benchmark/Benchmarks/TestAll.cs +++ b/src/Benchmark/Benchmarks/TestAll.cs @@ -1,65 +1,69 @@ -using Benchmark.Classes; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Classes; -namespace Benchmark.Benchmarks +namespace Mapster.Benchmark.Benchmarks { - public class TestAll + public class TestAll : MappingBenchmarkBase { private Foo _fooInstance; private Customer _customerInstance; - [Params(100_000)]//, 1_000_000)] - public int Iterations { get; set; } - - [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] + [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] public void MapsterTest() { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); - TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); + TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); } [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] public void RoslynTest() { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); - TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); + TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); } [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] public void FecTest() { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); - TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); + TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); } [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] public void CodegenTest() { - TestAdaptHelper.TestCodeGen(_fooInstance, Iterations); - TestAdaptHelper.TestCodeGen(_customerInstance, Iterations); + TestAdaptHelper.TestCodeGen(_fooInstance, MapOperations); + TestAdaptHelper.TestCodeGen(_customerInstance, MapOperations); + } + + [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] + public void AutoMapperTest() + { + TestAdaptHelper.TestAutoMapper(_fooInstance, MapOperations); + TestAdaptHelper.TestAutoMapper(_customerInstance, MapOperations); } - [Benchmark(Description = $"ExpressMapper {TestAdaptHelper.ExpressionMapperVersion}")] - public void ExpressMapperTest() + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] + public void FacetTest() { - TestAdaptHelper.TestExpressMapper(_fooInstance, Iterations); - TestAdaptHelper.TestExpressMapper(_customerInstance, Iterations); + TestAdaptHelper.TestFacet(_fooInstance, MapOperations); + TestAdaptHelper.TestFacet(_customerInstance, MapOperations); } - //[Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] - //public void AutoMapperTest() - //{ - // TestAdaptHelper.TestAutoMapper(_fooInstance, Iterations); - // TestAdaptHelper.TestAutoMapper(_customerInstance, Iterations); - //} + [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] + public void MapperlyTest() + { + TestAdaptHelper.TestMapperly(_fooInstance, MapOperations); + TestAdaptHelper.TestMapperly(_customerInstance, MapOperations); + } [GlobalSetup(Target = nameof(MapsterTest))] public void SetupMapster() { _fooInstance = TestAdaptHelper.SetupFooInstance(); _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Default); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Default); + TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Default); + TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Default); } [GlobalSetup(Target = nameof(RoslynTest))] @@ -67,8 +71,8 @@ public void SetupRoslyn() { _fooInstance = TestAdaptHelper.SetupFooInstance(); _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Roslyn); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Roslyn); + TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Roslyn); + TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Roslyn); } [GlobalSetup(Target = nameof(FecTest))] @@ -76,36 +80,44 @@ public void SetupFec() { _fooInstance = TestAdaptHelper.SetupFooInstance(); _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.FEC); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.FEC); + TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.FEC); + TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.FEC); } [GlobalSetup(Target = nameof(CodegenTest))] public void SetupCodegen() { - //_fooInstance = TestAdaptHelper.SetupFooInstance(); - //_customerInstance = TestAdaptHelper.SetupCustomerInstance(); - //FooMapper.Map(_fooInstance); - //CustomerMapper.Map(_customerInstance); + _fooInstance = TestAdaptHelper.SetupFooInstance(); + _customerInstance = TestAdaptHelper.SetupCustomerInstance(); + _ = FooMapper.Map(_fooInstance); + _ = CustomerMapper.Map(_customerInstance); } - [GlobalSetup(Target = nameof(ExpressMapperTest))] - public void SetupExpressMapper() + [GlobalSetup(Target = nameof(FacetTest))] + public void SetupFacet() { _fooInstance = TestAdaptHelper.SetupFooInstance(); _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureExpressMapper(_fooInstance); - TestAdaptHelper.ConfigureExpressMapper(_customerInstance); + TestAdaptHelper.ConfigureFacet(_fooInstance); + TestAdaptHelper.ConfigureFacet(_customerInstance); } - //[GlobalSetup(Target = nameof(AutoMapperTest))] - //public void SetupAutoMapper() - //{ - // _fooInstance = TestAdaptHelper.SetupFooInstance(); - // _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - // TestAdaptHelper.ConfigureAutoMapper(_fooInstance); - // TestAdaptHelper.ConfigureAutoMapper(_customerInstance); - //} + [GlobalSetup(Target = nameof(MapperlyTest))] + public void SetupMapperly() + { + _fooInstance = TestAdaptHelper.SetupFooInstance(); + _customerInstance = TestAdaptHelper.SetupCustomerInstance(); + TestAdaptHelper.ConfigureMapperly(_fooInstance); + TestAdaptHelper.ConfigureMapperly(_customerInstance); + } + [GlobalSetup(Target = nameof(AutoMapperTest))] + public void SetupAutoMapper() + { + _fooInstance = TestAdaptHelper.SetupFooInstance(); + _customerInstance = TestAdaptHelper.SetupCustomerInstance(); + TestAdaptHelper.ConfigureAutoMapper(_fooInstance); + TestAdaptHelper.ConfigureAutoMapper(_customerInstance); + } } } \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestComplexTypes.cs b/src/Benchmark/Benchmarks/TestComplexTypes.cs index a61ca9d3..71cb2e8a 100644 --- a/src/Benchmark/Benchmarks/TestComplexTypes.cs +++ b/src/Benchmark/Benchmarks/TestComplexTypes.cs @@ -1,91 +1,101 @@ -using Benchmark.Classes; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Classes; -namespace Benchmark.Benchmarks +namespace Mapster.Benchmark.Benchmarks { - public class TestComplexTypes + public class TestComplexTypes : MappingBenchmarkBase { private Customer _customerInstance; - [Params(1000, 10_000, 100_000, 1_000_000)] - public int Iterations { get; set; } - - [Benchmark] + [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] public void MapsterTest() { - TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); } - + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] public void RoslynTest() { - TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); } [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] public void FecTest() { - TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); } - [Benchmark] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] public void CodegenTest() { - TestAdaptHelper.TestCodeGen(_customerInstance, Iterations); + TestAdaptHelper.TestCodeGen(_customerInstance, MapOperations); + } + + [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] + public void AutoMapperTest() + { + TestAdaptHelper.TestAutoMapper(_customerInstance, MapOperations); } - [Benchmark] - public void ExpressMapperTest() + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] + public void FacetTest() { - TestAdaptHelper.TestExpressMapper(_customerInstance, Iterations); + TestAdaptHelper.TestFacet(_customerInstance, MapOperations); } - //[Benchmark] - //public void AutoMapperTest() - //{ - // TestAdaptHelper.TestAutoMapper(_customerInstance, Iterations); - //} + [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] + public void MapperlyTest() + { + TestAdaptHelper.TestMapperly(_customerInstance, MapOperations); + } [GlobalSetup(Target = nameof(MapsterTest))] public void SetupMapster() { _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Default); + TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Default); } [GlobalSetup(Target = nameof(RoslynTest))] public void SetupRoslyn() { _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Roslyn); + TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Roslyn); } [GlobalSetup(Target = nameof(FecTest))] public void SetupFec() { _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.FEC); + TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.FEC); } [GlobalSetup(Target = nameof(CodegenTest))] public void SetupCodegen() { - //_customerInstance = TestAdaptHelper.SetupCustomerInstance(); - //CustomerMapper.Map(_customerInstance); + _customerInstance = TestAdaptHelper.SetupCustomerInstance(); + _ = CustomerMapper.Map(_customerInstance); } - [GlobalSetup(Target = nameof(ExpressMapperTest))] - public void SetupExpressMapper() + [GlobalSetup(Target = nameof(FacetTest))] + public void SetupFacet() { _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureExpressMapper(_customerInstance); + TestAdaptHelper.ConfigureFacet(_customerInstance); } - //[GlobalSetup(Target = nameof(AutoMapperTest))] - //public void SetupAutoMapper() - //{ - // _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - // TestAdaptHelper.ConfigureAutoMapper(_customerInstance); - //} + [GlobalSetup(Target = nameof(MapperlyTest))] + public void SetupMapperly() + { + _customerInstance = TestAdaptHelper.SetupCustomerInstance(); + TestAdaptHelper.ConfigureMapperly(_customerInstance); + } + + [GlobalSetup(Target = nameof(AutoMapperTest))] + public void SetupAutoMapper() + { + _customerInstance = TestAdaptHelper.SetupCustomerInstance(); + TestAdaptHelper.ConfigureAutoMapper(_customerInstance); + } } } \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestSimpleTypes.cs b/src/Benchmark/Benchmarks/TestSimpleTypes.cs index 81f507e5..8f6466da 100644 --- a/src/Benchmark/Benchmarks/TestSimpleTypes.cs +++ b/src/Benchmark/Benchmarks/TestSimpleTypes.cs @@ -1,92 +1,101 @@ -using Benchmark.Classes; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Classes; -namespace Benchmark.Benchmarks +namespace Mapster.Benchmark.Benchmarks { - public class TestSimpleTypes + public class TestSimpleTypes : MappingBenchmarkBase { private Foo _fooInstance; - [Params(1000, 10_000, 100_000, 1_000_000)] - public int Iterations { get; set; } - - [Benchmark] + [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] public void MapsterTest() { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); } - + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] public void RoslynTest() { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); } [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] public void FecTest() { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); + TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); } - [Benchmark] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] public void CodegenTest() { - TestAdaptHelper.TestCodeGen(_fooInstance, Iterations); + TestAdaptHelper.TestCodeGen(_fooInstance, MapOperations); } - [Benchmark] - public void ExpressMapperTest() + [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] + public void AutoMapperTest() { - TestAdaptHelper.TestExpressMapper(_fooInstance, Iterations); + TestAdaptHelper.TestAutoMapper(_fooInstance, MapOperations); } - //[Benchmark] - //public void AutoMapperTest() - //{ - // TestAdaptHelper.TestAutoMapper(_fooInstance, Iterations); - //} + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] + public void FacetTest() + { + TestAdaptHelper.TestFacet(_fooInstance, MapOperations); + } + [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] + public void MapperlyTest() + { + TestAdaptHelper.TestMapperly(_fooInstance, MapOperations); + } [GlobalSetup(Target = nameof(MapsterTest))] public void SetupMapster() { _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Default); + TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Default); } [GlobalSetup(Target = nameof(RoslynTest))] public void SetupRoslyn() { _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Roslyn); + TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Roslyn); } [GlobalSetup(Target = nameof(FecTest))] public void SetupFec() { _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.FEC); + TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.FEC); } [GlobalSetup(Target = nameof(CodegenTest))] public void SetupCodegen() { - //_fooInstance = TestAdaptHelper.SetupFooInstance(); - //FooMapper.Map(_fooInstance); + _fooInstance = TestAdaptHelper.SetupFooInstance(); + _ = FooMapper.Map(_fooInstance); + } + + [GlobalSetup(Target = nameof(FacetTest))] + public void SetupFacet() + { + _fooInstance = TestAdaptHelper.SetupFooInstance(); + TestAdaptHelper.ConfigureFacet(_fooInstance); } - [GlobalSetup(Target = nameof(ExpressMapperTest))] - public void SetupExpressMapper() + [GlobalSetup(Target = nameof(MapperlyTest))] + public void SetupMapperly() { _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureExpressMapper(_fooInstance); + TestAdaptHelper.ConfigureMapperly(_fooInstance); } - //[GlobalSetup(Target = nameof(AutoMapperTest))] - //public void SetupAutoMapper() - //{ - // _fooInstance = TestAdaptHelper.SetupFooInstance(); - // TestAdaptHelper.ConfigureAutoMapper(_fooInstance); - //} + [GlobalSetup(Target = nameof(AutoMapperTest))] + public void SetupAutoMapper() + { + _fooInstance = TestAdaptHelper.SetupFooInstance(); + TestAdaptHelper.ConfigureAutoMapper(_fooInstance); + } } } \ No newline at end of file diff --git a/src/Benchmark/Classes/Customer.cs b/src/Benchmark/Classes/Customer.cs index 5fac9cef..30c832c6 100644 --- a/src/Benchmark/Classes/Customer.cs +++ b/src/Benchmark/Classes/Customer.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Benchmark.Classes +namespace Mapster.Benchmark.Classes { public class Address { diff --git a/src/Benchmark/Classes/Foo.cs b/src/Benchmark/Classes/Foo.cs index 063541b8..f12d3edb 100644 --- a/src/Benchmark/Classes/Foo.cs +++ b/src/Benchmark/Classes/Foo.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace Benchmark.Classes +namespace Mapster.Benchmark.Classes { public class Foo { diff --git a/src/Benchmark/Comparisons/FacetModels.cs b/src/Benchmark/Comparisons/FacetModels.cs new file mode 100644 index 00000000..ca6330b5 --- /dev/null +++ b/src/Benchmark/Comparisons/FacetModels.cs @@ -0,0 +1,22 @@ +using Facet; +using Mapster.Benchmark.Classes; + +namespace Mapster.Benchmark.Comparisons +{ + [Facet(typeof(Foo), NestedFacets = new[] { typeof(FooFacetDto) }, MaxDepth = 2)] + public partial class FooFacetDto + { + } + + [Facet(typeof(Address))] + public partial class AddressFacetDto + { + } + + [Facet(typeof(Customer), NestedFacets = new[] { typeof(AddressFacetDto) })] + public partial class CustomerFacetDto + { + [MapFrom("Address.City")] + public string AddressCity { get; set; } + } +} \ No newline at end of file diff --git a/src/Benchmark/Comparisons/MapperlyModels.cs b/src/Benchmark/Comparisons/MapperlyModels.cs new file mode 100644 index 00000000..32fac63a --- /dev/null +++ b/src/Benchmark/Comparisons/MapperlyModels.cs @@ -0,0 +1,62 @@ +using Mapster.Benchmark.Classes; +using Riok.Mapperly.Abstractions; + +namespace Mapster.Benchmark.Comparisons +{ + public class FooMapperlyDto + { + public string Name { get; set; } + public int Int32 { get; set; } + public long Int64 { get; set; } + public int? NullInt { get; set; } + public float Floatn { get; set; } + public double Doublen { get; set; } + public DateTime DateTime { get; set; } + public FooMapperlyDto Foo1 { get; set; } + public IEnumerable Foos { get; set; } + public FooMapperlyDto[] FooArr { get; set; } + public int[] IntArr { get; set; } + public IEnumerable Ints { get; set; } + } + + public class AddressMapperlyDto + { + public int Id { get; set; } + public string Street { get; set; } + public string City { get; set; } + public string Country { get; set; } + } + + public class AddressSummaryMapperlyDto + { + public int Id { get; set; } + public string City { get; set; } + public string Country { get; set; } + } + + public class CustomerMapperlyDto + { + public int Id { get; set; } + public string Name { get; set; } + public AddressMapperlyDto Address { get; set; } + public AddressSummaryMapperlyDto HomeAddress { get; set; } + public AddressSummaryMapperlyDto[] Addresses { get; set; } + public List WorkAddresses { get; set; } + public string AddressCity { get; set; } + } + + [Riok.Mapperly.Abstractions.Mapper(UseDeepCloning = true)] + public static partial class MapperlyMappings + { + public static partial AddressMapperlyDto MapAddress(Address source); + + [MapperIgnoreSource(nameof(Address.Street))] + public static partial AddressSummaryMapperlyDto MapAddressSummary(Address source); + + public static partial FooMapperlyDto MapFoo(Foo source); + + [MapperIgnoreSource(nameof(Customer.Credit))] + [MapProperty("Address.City", nameof(CustomerMapperlyDto.AddressCity))] + public static partial CustomerMapperlyDto MapCustomer(Customer source); + } +} \ No newline at end of file diff --git a/src/Benchmark/CustomerMapper.g.cs b/src/Benchmark/CustomerMapper.g.cs index 947ff47d..7b4dbd27 100644 --- a/src/Benchmark/CustomerMapper.g.cs +++ b/src/Benchmark/CustomerMapper.g.cs @@ -1,89 +1,88 @@ - -using System.Collections.Generic; -using Benchmark.Classes; +using System.Collections.Generic; +using Mapster.Benchmark.Classes; -namespace Benchmark +namespace Mapster.Benchmark { - public static partial class CustomerMapper - { - public static CustomerDTO Map(Customer p1) - { - return p1 == null ? null : new CustomerDTO() - { - Id = p1.Id, - Name = p1.Name, - Address = p1.Address == null ? null : new Address() - { - Id = p1.Address.Id, - Street = p1.Address.Street, - City = p1.Address.City, - Country = p1.Address.Country - }, - HomeAddress = p1.HomeAddress == null ? null : new AddressDTO() - { - Id = p1.HomeAddress.Id, - City = p1.HomeAddress.City, - Country = p1.HomeAddress.Country - }, - Addresses = func1(p1.Addresses), - WorkAddresses = func2(p1.WorkAddresses), - AddressCity = p1.Address == null ? null : p1.Address.City - }; - } - - private static AddressDTO[] func1(Address[] p2) - { - if (p2 == null) - { - return null; - } - AddressDTO[] result = new AddressDTO[p2.Length]; - - int v = 0; - - int i = 0; - int len = p2.Length; - - while (i < len) - { - Address item = p2[i]; - result[v++] = item == null ? null : new AddressDTO() - { - Id = item.Id, - City = item.City, - Country = item.Country - }; - i++; - } - return result; - - } - - private static List func2(ICollection
p3) - { - if (p3 == null) - { - return null; - } - List result = new List(p3.Count); - - ICollection list = result; - - IEnumerator
enumerator = p3.GetEnumerator(); - - while (enumerator.MoveNext()) - { - Address item = enumerator.Current; - list.Add(item == null ? null : new AddressDTO() - { - Id = item.Id, - City = item.City, - Country = item.Country - }); - } - return result; - - } - } -} + public static partial class CustomerMapper + { + public static CustomerDTO Map(Customer p1) + { + return p1 == null ? null : new CustomerDTO() + { + Id = p1.Id, + Name = p1.Name, + Address = p1.Address == null ? null : new Address() + { + Id = p1.Address.Id, + Street = p1.Address.Street, + City = p1.Address.City, + Country = p1.Address.Country + }, + HomeAddress = p1.HomeAddress == null ? null : new AddressDTO() + { + Id = p1.HomeAddress.Id, + City = p1.HomeAddress.City, + Country = p1.HomeAddress.Country + }, + Addresses = func1(p1.Addresses), + WorkAddresses = func2(p1.WorkAddresses), + AddressCity = p1.Address == null ? null : p1.Address.City + }; + } + + private static AddressDTO[] func1(Address[] p2) + { + if (p2 == null) + { + return null; + } + AddressDTO[] result = new AddressDTO[p2.Length]; + + int v = 0; + + int i = 0; + int len = p2.Length; + + while (i < len) + { + Address item = p2[i]; + result[v++] = item == null ? null : new AddressDTO() + { + Id = item.Id, + City = item.City, + Country = item.Country + }; + i++; + } + return result; + + } + + private static List func2(ICollection
p3) + { + if (p3 == null) + { + return null; + } + List result = new List(p3.Count); + + ICollection list = result; + + IEnumerator
enumerator = p3.GetEnumerator(); + + while (enumerator.MoveNext()) + { + Address item = enumerator.Current; + list.Add(item == null ? null : new AddressDTO() + { + Id = item.Id, + City = item.City, + Country = item.Country + }); + } + return result; + + } + } +} \ No newline at end of file diff --git a/src/Benchmark/CustomerMapper.tt b/src/Benchmark/CustomerMapper.tt index 7b316abd..3fa01aa8 100644 --- a/src/Benchmark/CustomerMapper.tt +++ b/src/Benchmark/CustomerMapper.tt @@ -8,7 +8,7 @@ <#@ Assembly Name="$(TargetDir)/$(ProjectName).dll" #> <#@ Assembly Name="$(TargetDir)/Mapster.dll" #> <#@ Assembly Name="$(TargetDir)/ExpressionTranslator.dll" #> -<#@ import namespace="Benchmark.Classes" #> +<#@ import namespace="Mapster.Benchmark.Classes" #> <#@ import namespace="ExpressionDebugger" #> <#@ import namespace="Mapster" #> <# @@ -18,7 +18,7 @@ { IsStatic = true, MethodName = "Map", - Namespace = "Benchmark", + Namespace = "Mapster.Benchmark", TypeName = "CustomerMapper" }; var code = foo.BuildAdapter() diff --git a/src/Benchmark/FooMapper.g.cs b/src/Benchmark/FooMapper.g.cs index 21edb8f9..dcebe0af 100644 --- a/src/Benchmark/FooMapper.g.cs +++ b/src/Benchmark/FooMapper.g.cs @@ -1,72 +1,71 @@ - -using System; +using System; using System.Linq; -using Benchmark.Classes; using Mapster; +using Mapster.Benchmark.Classes; using Mapster.Utils; -namespace Benchmark +namespace Mapster.Benchmark { - public static partial class FooMapper - { - public static Foo Map(Foo p1) - { - return p1 == null ? null : new Foo() - { - Name = p1.Name, - Int32 = p1.Int32, - Int64 = p1.Int64, - NullInt = p1.NullInt, - Floatn = p1.Floatn, - Doublen = p1.Doublen, - DateTime = p1.DateTime, - Foo1 = Map(p1.Foo1), - Foos = p1.Foos == null ? null : p1.Foos.Select(func1), - FooArr = func2(p1.FooArr), - IntArr = func3(p1.IntArr), - Ints = p1.Ints == null ? null : MapsterHelper.ToEnumerable(p1.Ints) - }; - } - - private static Foo func1(Foo p2) - { - return Map(p2); - } - - private static Foo[] func2(Foo[] p3) - { - if (p3 == null) - { - return null; - } - Foo[] result = new Foo[p3.Length]; - - int v = 0; - - int i = 0; - int len = p3.Length; - - while (i < len) - { - Foo item = p3[i]; - result[v++] = Map(item); - i++; - } - return result; - - } - - private static int[] func3(int[] p4) - { - if (p4 == null) - { - return null; - } - int[] result = new int[p4.Length]; - Array.Copy(p4, 0, result, 0, p4.Length); - return result; - - } - } -} + public static partial class FooMapper + { + public static Foo Map(Foo p1) + { + return p1 == null ? null : new Foo() + { + Name = p1.Name, + Int32 = p1.Int32, + Int64 = p1.Int64, + NullInt = p1.NullInt, + Floatn = p1.Floatn, + Doublen = p1.Doublen, + DateTime = p1.DateTime, + Foo1 = Map(p1.Foo1), + Foos = p1.Foos == null ? null : p1.Foos.Select(func1), + FooArr = func2(p1.FooArr), + IntArr = func3(p1.IntArr), + Ints = p1.Ints == null ? null : MapsterHelper.ToEnumerable(p1.Ints) + }; + } + + private static Foo func1(Foo p2) + { + return Map(p2); + } + + private static Foo[] func2(Foo[] p3) + { + if (p3 == null) + { + return null; + } + Foo[] result = new Foo[p3.Length]; + + int v = 0; + + int i = 0; + int len = p3.Length; + + while (i < len) + { + Foo item = p3[i]; + result[v++] = Map(item); + i++; + } + return result; + + } + + private static int[] func3(int[] p4) + { + if (p4 == null) + { + return null; + } + int[] result = new int[p4.Length]; + Array.Copy(p4, 0, result, 0, p4.Length); + return result; + + } + } +} \ No newline at end of file diff --git a/src/Benchmark/FooMapper.tt b/src/Benchmark/FooMapper.tt index 5279368d..003e94e1 100644 --- a/src/Benchmark/FooMapper.tt +++ b/src/Benchmark/FooMapper.tt @@ -8,7 +8,7 @@ <#@ Assembly Name="$(TargetDir)/Benchmark.dll" #> <#@ Assembly Name="$(TargetDir)/Mapster.dll" #> <#@ Assembly Name="$(TargetDir)/ExpressionTranslator.dll" #> -<#@ import namespace="Benchmark.Classes" #> +<#@ import namespace="Mapster.Benchmark.Classes" #> <#@ import namespace="ExpressionDebugger" #> <#@ import namespace="Mapster" #> <# @@ -18,7 +18,7 @@ { IsStatic = true, MethodName = "Map", - Namespace = "Benchmark", + Namespace = "Mapster.Benchmark", TypeName = "FooMapper" }; var code = foo.BuildAdapter() diff --git a/src/Benchmark/Program.cs b/src/Benchmark/Program.cs index 9c11da46..80c03f9a 100644 --- a/src/Benchmark/Program.cs +++ b/src/Benchmark/Program.cs @@ -1,7 +1,7 @@ -using Benchmark.Benchmarks; -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Running; +using Mapster.Benchmark.Benchmarks; -namespace Benchmark +namespace Mapster.Benchmark { class Program { diff --git a/src/Benchmark/TestAdaptHelper.cs b/src/Benchmark/TestAdaptHelper.cs index cf8918e2..3f23a6ee 100644 --- a/src/Benchmark/TestAdaptHelper.cs +++ b/src/Benchmark/TestAdaptHelper.cs @@ -1,26 +1,30 @@ -using Benchmark.Classes; +using AutoMapper; using FastExpressionCompiler; -using Mapster; -using System; -using System.Collections.Generic; +using Mapster.Benchmark.Classes; +using Mapster.Benchmark.Comparisons; using System.Linq.Expressions; -namespace Benchmark +namespace Mapster.Benchmark { public static class TestAdaptHelper { - //private static readonly IMapper _mapper = new Mapper(new MapperConfiguration(cfg => - //{ - // cfg.CreateMap(); - // cfg.CreateMap(); - // cfg.CreateMap(); - // cfg.CreateMap(); - //})); - - public const string MapsterVersion = "10.0.0"; - public const string AutoMapperVersion = "13.0.0"; - public const string ExpressionTranslatorVersion = "2.5.0"; - public const string ExpressionMapperVersion = "1.9.1"; + private static readonly MapperConfiguration AutoMapperConfiguration = new(cfg => + { + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap() + .ForMember(destination => destination.AddressCity, + options => options.MapFrom(source => source.Address != null ? source.Address.City : null)); + }); + + private static readonly IMapper AutoMapperInstance = CreateAutoMapper(); + private static readonly Func DefaultCompiler = TypeAdapterConfig.GlobalSettings.Compiler; + + public const string MapsterVersion = "10.0.7"; + public const string AutoMapperVersion = "14.0.0"; + public const string FacetVersion = "6.5.5"; + public const string MapperlyVersion = "4.3.1"; public static Customer SetupCustomerInstance() { @@ -72,82 +76,112 @@ public static Foo SetupFooInstance() }; } - private static readonly Func _defaultCompiler = TypeAdapterConfig.GlobalSettings.Compiler; + private static IMapper CreateAutoMapper() + { + AutoMapperConfiguration.AssertConfigurationIsValid(); + AutoMapperConfiguration.CompileMappings(); + return AutoMapperConfiguration.CreateMapper(); + } private static void SetupCompiler(MapsterCompilerType type) { TypeAdapterConfig.GlobalSettings.Compiler = type switch { - MapsterCompilerType.Default => _defaultCompiler, - MapsterCompilerType.Roslyn => exp => exp.CompileWithDebugInfo(), - MapsterCompilerType.FEC => exp => exp.CompileFast(), + MapsterCompilerType.Default => DefaultCompiler, + MapsterCompilerType.Roslyn => expression => expression.CompileWithDebugInfo(), + MapsterCompilerType.FEC => expression => expression.CompileFast(), _ => throw new ArgumentOutOfRangeException(nameof(type)), }; } - public static void ConfigureMapster(Foo fooInstance, MapsterCompilerType type) + + public static void ConfigureMapster(TSource sourceInstance, MapsterCompilerType type) + where TSource : class + where TDestination : class { SetupCompiler(type); - TypeAdapterConfig.GlobalSettings.Compile(typeof(Foo), typeof(Foo)); //recompile - fooInstance.Adapt(); //exercise + TypeAdapterConfig.GlobalSettings.Compile(typeof(TSource), typeof(TDestination)); + _ = sourceInstance.Adapt(); + } + + public static void ConfigureAutoMapper(TSource sourceInstance) + where TSource : class + { + _ = AutoMapperInstance.Map(sourceInstance); } - public static void ConfigureExpressMapper(Foo fooInstance) + + public static void ConfigureFacet(Foo sourceInstance) { - //ExpressMapper.Mapper.Map(fooInstance); //exercise + _ = new FooFacetDto(sourceInstance); } - //public static void ConfigureAutoMapper(Foo fooInstance) - //{ - // _mapper.Map(fooInstance); //exercise - //} - public static void ConfigureMapster(Customer customerInstance, MapsterCompilerType type) + public static void ConfigureFacet(Customer sourceInstance) { - SetupCompiler(type); - TypeAdapterConfig.GlobalSettings.Compile(typeof(Customer), typeof(CustomerDTO)); //recompile - customerInstance.Adapt(); //exercise + _ = new CustomerFacetDto(sourceInstance); + } + + public static void ConfigureMapperly(Foo sourceInstance) + { + _ = MapperlyMappings.MapFoo(sourceInstance); } - public static void ConfigureExpressMapper(Customer customerInstance) + + public static void ConfigureMapperly(Customer sourceInstance) { - //ExpressMapper.Mapper.Map(customerInstance); //exercise + _ = MapperlyMappings.MapCustomer(sourceInstance); } - //public static void ConfigureAutoMapper(Customer customerInstance) - //{ - // _mapper.Map(customerInstance); //exercise - //} - public static void TestMapsterAdapter(TSrc item, int iterations) + public static void TestMapsterAdapter(TSrc item, int mapOperations) where TSrc : class where TDest : class, new() { - Loop(item, get => get.Adapt(), iterations); + Loop(item, source => source.Adapt(), mapOperations); } - public static void TestExpressMapper(TSrc item, int iterations) + public static void TestAutoMapper(TSrc item, int mapOperations) where TSrc : class where TDest : class, new() { - //Loop(item, get => ExpressMapper.Mapper.Map(get), iterations); + Loop(item, source => AutoMapperInstance.Map(source), mapOperations); + } + + public static void TestFacet(Foo item, int mapOperations) + { + Loop(item, source => new FooFacetDto(source), mapOperations); + } + + public static void TestFacet(Customer item, int mapOperations) + { + Loop(item, source => new CustomerFacetDto(source), mapOperations); } - //public static void TestAutoMapper(TSrc item, int iterations) - // where TSrc : class - // where TDest : class, new() - //{ - // Loop(item, get => _mapper.Map(get), iterations); - //} + public static void TestMapperly(Foo item, int mapOperations) + { + Loop(item, MapperlyMappings.MapFoo, mapOperations); + } - public static void TestCodeGen(Foo item, int iterations) + public static void TestMapperly(Customer item, int mapOperations) { - //Loop(item, get => FooMapper.Map(get), iterations); + Loop(item, MapperlyMappings.MapCustomer, mapOperations); } - public static void TestCodeGen(Customer item, int iterations) + public static void TestCodeGen(Foo item, int mapOperations) { - //Loop(item, get => CustomerMapper.Map(get), iterations); + Loop(item, FooMapper.Map, mapOperations); } - private static void Loop(T item, Action action, int iterations) + public static void TestCodeGen(Customer item, int mapOperations) { - for (var i = 0; i < iterations; i++) action(item); + Loop(item, CustomerMapper.Map, mapOperations); + } + + private static void Loop(TSource item, Func map, int mapOperations) + { + TDestination result = default!; + for (var i = 0; i < mapOperations; i++) + { + result = map(item); + } + + GC.KeepAlive(result); } } From b72cc01a6fc1315836f91d627fd5e4c82408dfd0 Mon Sep 17 00:00:00 2001 From: Qyperion Date: Tue, 12 May 2026 17:25:09 +0300 Subject: [PATCH 2/5] docs: actualize benchmark result in docs --- README.md | 24 ++++++++++--------- docs/articles/packages/ExpressionDebugging.md | 10 +++----- .../packages/FastExpressionCompiler.md | 12 ++++++---- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 9f1c924c..5f9a107a 100644 --- a/README.md +++ b/README.md @@ -141,21 +141,23 @@ public static class StudentMapper { ### Performance & Memory efficient -Mapster was designed to be efficient on both speed and memory. You could gain a 4x performance improvement whilst using only 1/3 of memory. -And you could gain up to 12x faster performance with: +Mapster was designed to be efficient on both speed and memory. The repository includes a benchmark project in [`src/Benchmark`](src/Benchmark) that compares the local Mapster build, its compiler variants, and other modern mapping libraries like AutoMapper, Mapperly, and Facet. - [Roslyn Compiler](https://mapstermapper.github.io/Mapster/articles/packages/ExpressionDebugging.html) - [FEC](https://mapstermapper.github.io/Mapster/articles/packages/FastExpressionCompiler.html) - Code generation - -| Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | -|-------------------------- |----------:|----------:|----------:|-----------:|------:|------:|----------:| -| 'Mapster 6.0.0' | 108.59 ms | 1.198 ms | 1.811 ms | 31000.0000 | - | - | 124.36 MB | -| 'Mapster 6.0.0 (Roslyn)' | 38.45 ms | 0.494 ms | 0.830 ms | 31142.8571 | - | - | 124.36 MB | -| 'Mapster 6.0.0 (FEC)' | 37.03 ms | 0.281 ms | 0.472 ms | 29642.8571 | - | - | 118.26 MB | -| 'Mapster 6.0.0 (Codegen)' | 34.16 ms | 0.209 ms | 0.316 ms | 31133.3333 | - | - | 124.36 MB | -| 'ExpressMapper 1.9.1' | 205.78 ms | 5.357 ms | 8.098 ms | 59000.0000 | - | - | 236.51 MB | -| 'AutoMapper 10.0.0' | 420.97 ms | 23.266 ms | 35.174 ms | 87000.0000 | - | - | 350.95 MB | +- Facet +- Mapperly + +| Method | MapOperations | Mean | StdDev | Error | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | +| -------- | -------------- | -----: | -------: | ------: | ------: | -----: | -----: | ----------: | ----------: | +| `Mapster 10.0.7` | 1000000 | 412,534 us | 2,704 us | 4,543 us | 1.00 | 77000 | - | 1243.59 MB | 1.00 | +| `Mapster 10.0.7 (Roslyn)` | 1000000 | 397,028 us | 5,174 us | 8,695 us | 0.96 | 75000 | - | 1205.44 MB | 0.97 | +| `Mapster 10.0.7 (FEC)` | 1000000 | 124,374 us | 1,290 us | 2,466 us | 0.30 | 74000 | - | 1182.56 MB | 0.95 | +| `Mapster 10.0.7 (Codegen)` | 1000000 | 105,214 us | 1,312 us | 2,206 us | 0.26 | 75500 | 166 | 1205.44 MB | 0.97 | +| `AutoMapper 14.0.0` | 1000000 | 600,077 us | 63,170 us | 95,505 us | 1.45 | 197000 | 1000 | 3158.59 MB | 2.54 | +| `Facet 6.5.5` | 1000000 | 628,280 us | 7,326 us | 11,076 us | 1.52 | 325000 | 1000 | 5187.99 MB | 4.17 | +| `Mapperly 4.3.1` | 1000000 | 128,521 us | 1,453 us | 2,442 us | 0.31 | 94500 | 250 | 1510.62 MB | 1.21 | ### Step into debugging diff --git a/docs/articles/packages/ExpressionDebugging.md b/docs/articles/packages/ExpressionDebugging.md index e32f41dd..189d3ce9 100644 --- a/docs/articles/packages/ExpressionDebugging.md +++ b/docs/articles/packages/ExpressionDebugging.md @@ -50,12 +50,8 @@ TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileWithDebugInfo(opt) var dto = poco.Adapt(); //<-- you can step-into this function!! ``` -### Do not worry about performance +### Performance notes -In `RELEASE` mode, Roslyn compiler is actually faster than default dynamic compilation by 2x. -Here is the result: +In modern .NET runtimes, the Roslyn compiler path is mostly useful for step-into debugging and inspecting generated mapping code. In the current benchmark snapshot it performs close to the default Mapster compiler in steady-state execution. -| Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | -|-------------------------- |---------------:|-------------:|-------------:|------------:|------:|------:|-----------:| -| 'Mapster 4.1.1' | 115.31 ms | 0.849 ms | 1.426 ms | 31000.0000 | - | - | 124.36 MB | -| 'Mapster 4.1.1 (Roslyn)' | 53.55 ms | 0.342 ms | 0.654 ms | 31100.0000 | - | - | 124.36 MB | +See the [benchmark snapshot in README](../../../README.md#performance--memory-efficient) for current numbers. diff --git a/docs/articles/packages/FastExpressionCompiler.md b/docs/articles/packages/FastExpressionCompiler.md index a20df828..037d16aa 100644 --- a/docs/articles/packages/FastExpressionCompiler.md +++ b/docs/articles/packages/FastExpressionCompiler.md @@ -19,9 +19,11 @@ Then add following code on start up TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileFast(); ``` -That's it. Now your code will enjoy performance boost. Here is result. +That's it. Now your code will enjoy performance boost. Here is a current benchmark snapshot: -| Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | -|-------------------------- |---------------:|-------------:|-------------:|------------:|------:|------:|-----------:| -| 'Mapster 4.1.1' | 115.31 ms | 0.849 ms | 1.426 ms | 31000.0000 | - | - | 124.36 MB | -| 'Mapster 4.1.1 (FEC)' | 54.70 ms | 1.023 ms | 1.546 ms | 29600.0000 | - | - | 118.26 MB | +| Method | MapOperations | Mean | StdDev | Error | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | +| -------- | -------------- | -----: | -------: | ------: | ------: | -----: | -----: | ----------: | ----------: | +| `Mapster 10.0.7` | 1000000 | 412,534 us | 2,704 us | 4,543 us | 1.00 | 77000 | - | 1243.59 MB | 1.00 | +| `Mapster 10.0.7 (FEC)` | 1000000 | 124,374 us | 1,290 us | 2,466 us | 0.30 | 74000 | - | 1182.56 MB | 0.95 | + +See the [benchmark snapshot in README](../../../README.md#performance--memory-efficient) for the full comparison. From d856d0ee6cb4405241e021b1e19606e268e028cc Mon Sep 17 00:00:00 2001 From: Qyperion Date: Sat, 16 May 2026 00:22:18 +0300 Subject: [PATCH 3/5] feat(benchmark): refactor benchmarks: add flat, recursive, complex scenarios --- src/Benchmark/Benchmark.csproj | 13 +- src/Benchmark/Benchmarks/Config.cs | 3 + .../Benchmarks/MappingBenchmarkBase.cs | 2 +- src/Benchmark/Benchmarks/PerMapColumn.cs | 86 ++++++ src/Benchmark/Benchmarks/ScenarioColumn.cs | 37 +++ src/Benchmark/Benchmarks/TestAll.cs | 123 -------- src/Benchmark/Benchmarks/TestComplexTypes.cs | 90 +++--- src/Benchmark/Benchmarks/TestFlatTypes.cs | 87 ++++++ .../Benchmarks/TestRecursiveTypes.cs | 86 ++++++ src/Benchmark/Benchmarks/TestSimpleTypes.cs | 101 ------- src/Benchmark/Benchmarks/TestTotalAllTypes.cs | 143 ++++++++++ src/Benchmark/Classes/Customer.cs | 32 ++- src/Benchmark/Classes/Foo.cs | 29 ++ src/Benchmark/Classes/Person.cs | 28 ++ src/Benchmark/Comparisons/FacetModels.cs | 5 + src/Benchmark/Comparisons/MapperlyModels.cs | 14 + src/Benchmark/FooMapper.g.cs | 16 +- src/Benchmark/FooMapper.tt | 4 +- src/Benchmark/PersonMapper.g.cs | 23 ++ src/Benchmark/PersonMapper.tt | 25 ++ src/Benchmark/Program.cs | 5 +- src/Benchmark/TestAdaptHelper.cs | 263 +++++++----------- 22 files changed, 743 insertions(+), 472 deletions(-) create mode 100644 src/Benchmark/Benchmarks/PerMapColumn.cs create mode 100644 src/Benchmark/Benchmarks/ScenarioColumn.cs delete mode 100644 src/Benchmark/Benchmarks/TestAll.cs create mode 100644 src/Benchmark/Benchmarks/TestFlatTypes.cs create mode 100644 src/Benchmark/Benchmarks/TestRecursiveTypes.cs delete mode 100644 src/Benchmark/Benchmarks/TestSimpleTypes.cs create mode 100644 src/Benchmark/Benchmarks/TestTotalAllTypes.cs create mode 100644 src/Benchmark/Classes/Person.cs create mode 100644 src/Benchmark/PersonMapper.g.cs create mode 100644 src/Benchmark/PersonMapper.tt diff --git a/src/Benchmark/Benchmark.csproj b/src/Benchmark/Benchmark.csproj index 451136bd..400a510f 100644 --- a/src/Benchmark/Benchmark.csproj +++ b/src/Benchmark/Benchmark.csproj @@ -12,10 +12,12 @@ - + + + @@ -35,6 +37,11 @@ True FooMapper.tt + + True + True + PersonMapper.tt + @@ -46,6 +53,10 @@ TextTemplatingFileGenerator FooMapper.g.cs + + TextTemplatingFileGenerator + PersonMapper.g.cs + diff --git a/src/Benchmark/Benchmarks/Config.cs b/src/Benchmark/Benchmarks/Config.cs index fb45726c..82bff156 100644 --- a/src/Benchmark/Benchmarks/Config.cs +++ b/src/Benchmark/Benchmarks/Config.cs @@ -19,16 +19,19 @@ public Config() AddExporter(HtmlExporter.Default); AddDiagnoser(MemoryDiagnoser.Default); + AddColumn(ScenarioColumn.Default); AddColumn(TargetMethodColumn.Method); AddColumnProvider(DefaultColumnProviders.Params); AddColumn(StatisticColumn.Mean); + AddColumn(PerMapColumn.Nanoseconds); AddColumn(StatisticColumn.StdDev); AddColumn(StatisticColumn.Error); AddColumn(BaselineRatioColumn.RatioMean); AddColumn(BaselineAllocationRatioColumn.RatioMean); AddColumnProvider(DefaultColumnProviders.Metrics); + AddColumn(PerMapColumn.Bytes); AddJob(Job.ShortRun .WithLaunchCount(1) diff --git a/src/Benchmark/Benchmarks/MappingBenchmarkBase.cs b/src/Benchmark/Benchmarks/MappingBenchmarkBase.cs index e5f5ab77..932dbafa 100644 --- a/src/Benchmark/Benchmarks/MappingBenchmarkBase.cs +++ b/src/Benchmark/Benchmarks/MappingBenchmarkBase.cs @@ -4,7 +4,7 @@ namespace Mapster.Benchmark.Benchmarks { public abstract class MappingBenchmarkBase { - public IEnumerable MapOperationValues => new[] { 1_000, 10_000, 100_000, 1_000_000 }; + public IEnumerable MapOperationValues => new[] { 10_000, 100_000, 1_000_000 }; [ParamsSource(nameof(MapOperationValues))] public int MapOperations { get; set; } diff --git a/src/Benchmark/Benchmarks/PerMapColumn.cs b/src/Benchmark/Benchmarks/PerMapColumn.cs new file mode 100644 index 00000000..2eebfef2 --- /dev/null +++ b/src/Benchmark/Benchmarks/PerMapColumn.cs @@ -0,0 +1,86 @@ +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; + +namespace Mapster.Benchmark.Benchmarks +{ + public abstract class PerMapColumn : IColumn + { + public static readonly IColumn Nanoseconds = new NanosecondsPerMapColumn(); + public static readonly IColumn Bytes = new BytesPerMapColumn(); + + public abstract string Id { get; } + public abstract string ColumnName { get; } + public abstract string Legend { get; } + public abstract ColumnCategory Category { get; } + + public UnitType UnitType => UnitType.Dimensionless; + public int PriorityInCategory => 100; + public bool IsNumeric => true; + public bool AlwaysShow => true; + public bool IsAvailable(Summary summary) => true; + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; + + public abstract string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style); + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + => GetValue(summary, benchmarkCase, summary.Style); + + protected static long GetLogicalMapCount(BenchmarkCase benchmarkCase) + { + var mapOperations = 1; + var parameter = benchmarkCase.Parameters.Items + .FirstOrDefault(p => p.Name == nameof(MappingBenchmarkBase.MapOperations)); + + if (parameter?.Value is int value && value > 0) + mapOperations = value; + + //TestTotalAllTypes includes 3 separate mapping calls per benchmark call + var mappingsPerBenchmarkCall = benchmarkCase.Descriptor.Type == typeof(TestTotalAllTypes) ? 3 : 1; + return (long)mapOperations * mappingsPerBenchmarkCall; + } + + protected static string Format(double value, SummaryStyle style) + => value.ToString("0.###", style.CultureInfo); + + public override string ToString() => ColumnName; + + private sealed class NanosecondsPerMapColumn : PerMapColumn + { + public override string Id => nameof(NanosecondsPerMapColumn); + public override string ColumnName => "Ns/Map"; + public override string Legend => "Mean nanoseconds per single mapping call"; + public override ColumnCategory Category => ColumnCategory.Statistics; + + public override string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) + { + if (!summary.HasReport(benchmarkCase)) + return "?"; + + var meanNanoseconds = summary[benchmarkCase].ResultStatistics?.Mean; + return meanNanoseconds.HasValue + ? Format(meanNanoseconds.Value / GetLogicalMapCount(benchmarkCase), style) + : "?"; + } + } + + private sealed class BytesPerMapColumn : PerMapColumn + { + public override string Id => nameof(BytesPerMapColumn); + public override string ColumnName => "Bytes/Map"; + public override string Legend => "Allocated bytes per single mapping call"; + public override ColumnCategory Category => ColumnCategory.Metric; + + public override string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) + { + if (!summary.HasReport(benchmarkCase)) + return "?"; + + var allocatedBytesPerBenchmarkCall = summary[benchmarkCase].GcStats.GetBytesAllocatedPerOperation(benchmarkCase); + return allocatedBytesPerBenchmarkCall.HasValue + ? Format((double)allocatedBytesPerBenchmarkCall.Value / GetLogicalMapCount(benchmarkCase), style) + : "?"; + } + } + } +} \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/ScenarioColumn.cs b/src/Benchmark/Benchmarks/ScenarioColumn.cs new file mode 100644 index 00000000..67362598 --- /dev/null +++ b/src/Benchmark/Benchmarks/ScenarioColumn.cs @@ -0,0 +1,37 @@ +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; + +namespace Mapster.Benchmark.Benchmarks +{ + /// + /// Adds a "Scenario" column to the joined summary so each row clearly indicates which benchmark class + /// (TestFlatTypes / TestRecursiveTypes / TestComplexTypes / TestTotalAllTypes) produced it. + /// + public class ScenarioColumn : IColumn + { + public static readonly IColumn Default = new ScenarioColumn(); + + public string Id => nameof(ScenarioColumn); + public string ColumnName => "Scenario"; + public string Legend => "Benchmark class the row belongs to"; + public UnitType UnitType => UnitType.Dimensionless; + public ColumnCategory Category => ColumnCategory.Job; + public int PriorityInCategory => -10; + public bool IsNumeric => false; + public bool AlwaysShow => true; + public bool IsAvailable(Summary summary) => true; + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + { + var name = benchmarkCase.Descriptor.Type.Name; + return name.StartsWith("Test") ? name[4..] : name; + } + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) + => GetValue(summary, benchmarkCase); + + public override string ToString() => ColumnName; + } +} diff --git a/src/Benchmark/Benchmarks/TestAll.cs b/src/Benchmark/Benchmarks/TestAll.cs deleted file mode 100644 index f2ff5e0a..00000000 --- a/src/Benchmark/Benchmarks/TestAll.cs +++ /dev/null @@ -1,123 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Mapster.Benchmark.Classes; - -namespace Mapster.Benchmark.Benchmarks -{ - public class TestAll : MappingBenchmarkBase - { - private Foo _fooInstance; - private Customer _customerInstance; - - [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] - public void MapsterTest() - { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); - TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); - } - - [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] - public void RoslynTest() - { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); - TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); - } - - [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] - public void FecTest() - { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); - TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); - } - - [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] - public void CodegenTest() - { - TestAdaptHelper.TestCodeGen(_fooInstance, MapOperations); - TestAdaptHelper.TestCodeGen(_customerInstance, MapOperations); - } - - [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] - public void AutoMapperTest() - { - TestAdaptHelper.TestAutoMapper(_fooInstance, MapOperations); - TestAdaptHelper.TestAutoMapper(_customerInstance, MapOperations); - } - - [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] - public void FacetTest() - { - TestAdaptHelper.TestFacet(_fooInstance, MapOperations); - TestAdaptHelper.TestFacet(_customerInstance, MapOperations); - } - - [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] - public void MapperlyTest() - { - TestAdaptHelper.TestMapperly(_fooInstance, MapOperations); - TestAdaptHelper.TestMapperly(_customerInstance, MapOperations); - } - - [GlobalSetup(Target = nameof(MapsterTest))] - public void SetupMapster() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Default); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Default); - } - - [GlobalSetup(Target = nameof(RoslynTest))] - public void SetupRoslyn() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Roslyn); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Roslyn); - } - - [GlobalSetup(Target = nameof(FecTest))] - public void SetupFec() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.FEC); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.FEC); - } - - [GlobalSetup(Target = nameof(CodegenTest))] - public void SetupCodegen() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - _ = FooMapper.Map(_fooInstance); - _ = CustomerMapper.Map(_customerInstance); - } - - [GlobalSetup(Target = nameof(FacetTest))] - public void SetupFacet() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureFacet(_fooInstance); - TestAdaptHelper.ConfigureFacet(_customerInstance); - } - - [GlobalSetup(Target = nameof(MapperlyTest))] - public void SetupMapperly() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapperly(_fooInstance); - TestAdaptHelper.ConfigureMapperly(_customerInstance); - } - - [GlobalSetup(Target = nameof(AutoMapperTest))] - public void SetupAutoMapper() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureAutoMapper(_fooInstance); - TestAdaptHelper.ConfigureAutoMapper(_customerInstance); - } - } -} \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestComplexTypes.cs b/src/Benchmark/Benchmarks/TestComplexTypes.cs index 71cb2e8a..4f27a54b 100644 --- a/src/Benchmark/Benchmarks/TestComplexTypes.cs +++ b/src/Benchmark/Benchmarks/TestComplexTypes.cs @@ -1,101 +1,87 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using Mapster.Benchmark.Classes; +using Mapster.Benchmark.Comparisons; namespace Mapster.Benchmark.Benchmarks { + // Customer/CustomerDTO: nested object of different type, two collection shape changes + // (Address[] -> AddressDTO[], ICollection
-> List) and a flattening rule (AddressCity <- Address.City). public class TestComplexTypes : MappingBenchmarkBase { - private Customer _customerInstance; + private static readonly Func CustomerFacetCompiled = + CustomerFacetDto.Projection.Compile(); + + private Customer _customer; [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] public void MapsterTest() - { - TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); - } + => TestAdaptHelper.Loop(_customer, src => src.Adapt(), MapOperations); [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] public void RoslynTest() - { - TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); - } + => TestAdaptHelper.Loop(_customer, src => src.Adapt(), MapOperations); [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] public void FecTest() - { - TestAdaptHelper.TestMapsterAdapter(_customerInstance, MapOperations); - } + => TestAdaptHelper.Loop(_customer, src => src.Adapt(), MapOperations); [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] public void CodegenTest() - { - TestAdaptHelper.TestCodeGen(_customerInstance, MapOperations); - } + => TestAdaptHelper.Loop(_customer, CustomerMapper.Map, MapOperations); [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] public void AutoMapperTest() - { - TestAdaptHelper.TestAutoMapper(_customerInstance, MapOperations); - } + => TestAdaptHelper.Loop(_customer, src => TestAdaptHelper.AutoMapper.Map(src), MapOperations); [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] public void FacetTest() - { - TestAdaptHelper.TestFacet(_customerInstance, MapOperations); - } + => TestAdaptHelper.Loop(_customer, src => new CustomerFacetDto(src), MapOperations); + + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion} (Compiled Projection)")] + public void FacetCompiledTest() + => TestAdaptHelper.Loop(_customer, CustomerFacetCompiled, MapOperations); [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] public void MapperlyTest() - { - TestAdaptHelper.TestMapperly(_customerInstance, MapOperations); - } + => TestAdaptHelper.Loop(_customer, MapperlyMappings.MapCustomer, MapOperations); [GlobalSetup(Target = nameof(MapsterTest))] public void SetupMapster() { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Default); + _customer = TestAdaptHelper.SetupCustomerInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.Default); + _ = _customer.Adapt(); } [GlobalSetup(Target = nameof(RoslynTest))] public void SetupRoslyn() { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.Roslyn); + _customer = TestAdaptHelper.SetupCustomerInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.Roslyn); + _ = _customer.Adapt(); } [GlobalSetup(Target = nameof(FecTest))] public void SetupFec() { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapster(_customerInstance, MapsterCompilerType.FEC); + _customer = TestAdaptHelper.SetupCustomerInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.FEC); + _ = _customer.Adapt(); } [GlobalSetup(Target = nameof(CodegenTest))] - public void SetupCodegen() - { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - _ = CustomerMapper.Map(_customerInstance); - } + public void SetupCodegen() => _customer = TestAdaptHelper.SetupCustomerInstance(); + + [GlobalSetup(Target = nameof(AutoMapperTest))] + public void SetupAutoMapper() => _customer = TestAdaptHelper.SetupCustomerInstance(); [GlobalSetup(Target = nameof(FacetTest))] - public void SetupFacet() - { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureFacet(_customerInstance); - } + public void SetupFacet() => _customer = TestAdaptHelper.SetupCustomerInstance(); - [GlobalSetup(Target = nameof(MapperlyTest))] - public void SetupMapperly() - { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureMapperly(_customerInstance); - } + [GlobalSetup(Target = nameof(FacetCompiledTest))] + public void SetupFacetCompiled() => _customer = TestAdaptHelper.SetupCustomerInstance(); - [GlobalSetup(Target = nameof(AutoMapperTest))] - public void SetupAutoMapper() - { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureAutoMapper(_customerInstance); - } + [GlobalSetup(Target = nameof(MapperlyTest))] + public void SetupMapperly() => _customer = TestAdaptHelper.SetupCustomerInstance(); } -} \ No newline at end of file +} diff --git a/src/Benchmark/Benchmarks/TestFlatTypes.cs b/src/Benchmark/Benchmarks/TestFlatTypes.cs new file mode 100644 index 00000000..f860894f --- /dev/null +++ b/src/Benchmark/Benchmarks/TestFlatTypes.cs @@ -0,0 +1,87 @@ +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Classes; +using Mapster.Benchmark.Comparisons; + +namespace Mapster.Benchmark.Benchmarks +{ + // FlatType DTO: simple property-to-property copy, no nesting, no collections. + // Highlights pure per-call overhead (delegate dispatch, allocation rate, IL quality of property copy). + public class TestFlatTypes : MappingBenchmarkBase + { + private static readonly Func PersonFacetCompiled = + PersonFacetDto.Projection.Compile(); + + private Person _person; + + [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] + public void MapsterTest() + => TestAdaptHelper.Loop(_person, src => src.Adapt(), MapOperations); + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] + public void RoslynTest() + => TestAdaptHelper.Loop(_person, src => src.Adapt(), MapOperations); + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] + public void FecTest() + => TestAdaptHelper.Loop(_person, src => src.Adapt(), MapOperations); + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] + public void CodegenTest() + => TestAdaptHelper.Loop(_person, PersonMapper.Map, MapOperations); + + [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] + public void AutoMapperTest() + => TestAdaptHelper.Loop(_person, src => TestAdaptHelper.AutoMapper.Map(src), MapOperations); + + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] + public void FacetTest() + => TestAdaptHelper.Loop(_person, src => new PersonFacetDto(src), MapOperations); + + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion} (Compiled Projection)")] + public void FacetCompiledTest() + => TestAdaptHelper.Loop(_person, PersonFacetCompiled, MapOperations); + + [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] + public void MapperlyTest() + => TestAdaptHelper.Loop(_person, MapperlyMappings.MapPerson, MapOperations); + + [GlobalSetup(Target = nameof(MapsterTest))] + public void SetupMapster() + { + _person = TestAdaptHelper.SetupPersonInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.Default); + _ = _person.Adapt(); + } + + [GlobalSetup(Target = nameof(RoslynTest))] + public void SetupRoslyn() + { + _person = TestAdaptHelper.SetupPersonInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.Roslyn); + _ = _person.Adapt(); + } + + [GlobalSetup(Target = nameof(FecTest))] + public void SetupFec() + { + _person = TestAdaptHelper.SetupPersonInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.FEC); + _ = _person.Adapt(); + } + + [GlobalSetup(Target = nameof(CodegenTest))] + public void SetupCodegen() => _person = TestAdaptHelper.SetupPersonInstance(); + + [GlobalSetup(Target = nameof(AutoMapperTest))] + public void SetupAutoMapper() => _person = TestAdaptHelper.SetupPersonInstance(); + + [GlobalSetup(Target = nameof(FacetTest))] + public void SetupFacet() => _person = TestAdaptHelper.SetupPersonInstance(); + + [GlobalSetup(Target = nameof(FacetCompiledTest))] + public void SetupFacetCompiled() => _person = TestAdaptHelper.SetupPersonInstance(); + + [GlobalSetup(Target = nameof(MapperlyTest))] + public void SetupMapperly() => _person = TestAdaptHelper.SetupPersonInstance(); + } +} \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestRecursiveTypes.cs b/src/Benchmark/Benchmarks/TestRecursiveTypes.cs new file mode 100644 index 00000000..b6693bdc --- /dev/null +++ b/src/Benchmark/Benchmarks/TestRecursiveTypes.cs @@ -0,0 +1,86 @@ +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Classes; +using Mapster.Benchmark.Comparisons; + +namespace Mapster.Benchmark.Benchmarks +{ + // Self-recursive graph with nested references and collections. + // Source: Foo, Destination: FooDTO. + public class TestRecursiveTypes : MappingBenchmarkBase + { + private static readonly Func FooFacetCompiled = FooFacetDto.Projection.Compile(); + + private Foo _foo; + + [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] + public void MapsterTest() + => TestAdaptHelper.Loop(_foo, src => src.Adapt(), MapOperations); + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] + public void RoslynTest() + => TestAdaptHelper.Loop(_foo, src => src.Adapt(), MapOperations); + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] + public void FecTest() + => TestAdaptHelper.Loop(_foo, src => src.Adapt(), MapOperations); + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] + public void CodegenTest() + => TestAdaptHelper.Loop(_foo, FooMapper.Map, MapOperations); + + [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] + public void AutoMapperTest() + => TestAdaptHelper.Loop(_foo, src => TestAdaptHelper.AutoMapper.Map(src), MapOperations); + + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] + public void FacetTest() + => TestAdaptHelper.Loop(_foo, src => new FooFacetDto(src), MapOperations); + + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion} (Compiled Projection)")] + public void FacetCompiledTest() + => TestAdaptHelper.Loop(_foo, FooFacetCompiled, MapOperations); + + [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] + public void MapperlyTest() + => TestAdaptHelper.Loop(_foo, MapperlyMappings.MapFoo, MapOperations); + + [GlobalSetup(Target = nameof(MapsterTest))] + public void SetupMapster() + { + _foo = TestAdaptHelper.SetupFooInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.Default); + _ = _foo.Adapt(); + } + + [GlobalSetup(Target = nameof(RoslynTest))] + public void SetupRoslyn() + { + _foo = TestAdaptHelper.SetupFooInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.Roslyn); + _ = _foo.Adapt(); + } + + [GlobalSetup(Target = nameof(FecTest))] + public void SetupFec() + { + _foo = TestAdaptHelper.SetupFooInstance(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.FEC); + _ = _foo.Adapt(); + } + + [GlobalSetup(Target = nameof(CodegenTest))] + public void SetupCodegen() => _foo = TestAdaptHelper.SetupFooInstance(); + + [GlobalSetup(Target = nameof(AutoMapperTest))] + public void SetupAutoMapper() => _foo = TestAdaptHelper.SetupFooInstance(); + + [GlobalSetup(Target = nameof(FacetTest))] + public void SetupFacet() => _foo = TestAdaptHelper.SetupFooInstance(); + + [GlobalSetup(Target = nameof(FacetCompiledTest))] + public void SetupFacetCompiled() => _foo = TestAdaptHelper.SetupFooInstance(); + + [GlobalSetup(Target = nameof(MapperlyTest))] + public void SetupMapperly() => _foo = TestAdaptHelper.SetupFooInstance(); + } +} \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestSimpleTypes.cs b/src/Benchmark/Benchmarks/TestSimpleTypes.cs deleted file mode 100644 index 8f6466da..00000000 --- a/src/Benchmark/Benchmarks/TestSimpleTypes.cs +++ /dev/null @@ -1,101 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Mapster.Benchmark.Classes; - -namespace Mapster.Benchmark.Benchmarks -{ - public class TestSimpleTypes : MappingBenchmarkBase - { - private Foo _fooInstance; - - [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] - public void MapsterTest() - { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); - } - - [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] - public void RoslynTest() - { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); - } - - [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] - public void FecTest() - { - TestAdaptHelper.TestMapsterAdapter(_fooInstance, MapOperations); - } - - [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] - public void CodegenTest() - { - TestAdaptHelper.TestCodeGen(_fooInstance, MapOperations); - } - - [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] - public void AutoMapperTest() - { - TestAdaptHelper.TestAutoMapper(_fooInstance, MapOperations); - } - - [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] - public void FacetTest() - { - TestAdaptHelper.TestFacet(_fooInstance, MapOperations); - } - - [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] - public void MapperlyTest() - { - TestAdaptHelper.TestMapperly(_fooInstance, MapOperations); - } - - [GlobalSetup(Target = nameof(MapsterTest))] - public void SetupMapster() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Default); - } - - [GlobalSetup(Target = nameof(RoslynTest))] - public void SetupRoslyn() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.Roslyn); - } - - [GlobalSetup(Target = nameof(FecTest))] - public void SetupFec() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureMapster(_fooInstance, MapsterCompilerType.FEC); - } - - [GlobalSetup(Target = nameof(CodegenTest))] - public void SetupCodegen() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _ = FooMapper.Map(_fooInstance); - } - - [GlobalSetup(Target = nameof(FacetTest))] - public void SetupFacet() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureFacet(_fooInstance); - } - - [GlobalSetup(Target = nameof(MapperlyTest))] - public void SetupMapperly() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureMapperly(_fooInstance); - } - - [GlobalSetup(Target = nameof(AutoMapperTest))] - public void SetupAutoMapper() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureAutoMapper(_fooInstance); - } - } -} \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestTotalAllTypes.cs b/src/Benchmark/Benchmarks/TestTotalAllTypes.cs new file mode 100644 index 00000000..22c1c295 --- /dev/null +++ b/src/Benchmark/Benchmarks/TestTotalAllTypes.cs @@ -0,0 +1,143 @@ +using BenchmarkDotNet.Attributes; +using Mapster.Benchmark.Classes; +using Mapster.Benchmark.Comparisons; + +namespace Mapster.Benchmark.Benchmarks +{ + /// + /// Total benchmark across all three sample shapes: + /// + /// -> (FlatType DTO) + /// -> (self-recursive graph) + /// -> (nested + collections + flattening) + /// + /// Each [Benchmark] iteration runs all three scenarios via + /// loops, so the reported Mean is their total time. + /// + public class TestTotalAllTypes : MappingBenchmarkBase + { + private static readonly Func FooFacetCompiled = FooFacetDto.Projection.Compile(); + private static readonly Func CustomerFacetCompiled = CustomerFacetDto.Projection.Compile(); + private static readonly Func PersonFacetCompiled = PersonFacetDto.Projection.Compile(); + + private Person _person; + private Foo _foo; + private Customer _customer; + + [Benchmark(Baseline = true, Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] + public void MapsterTest() + { + TestAdaptHelper.Loop(_person, src => src.Adapt(), MapOperations); + TestAdaptHelper.Loop(_foo, src => src.Adapt(), MapOperations); + TestAdaptHelper.Loop(_customer, src => src.Adapt(), MapOperations); + } + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] + public void RoslynTest() + { + TestAdaptHelper.Loop(_person, src => src.Adapt(), MapOperations); + TestAdaptHelper.Loop(_foo, src => src.Adapt(), MapOperations); + TestAdaptHelper.Loop(_customer, src => src.Adapt(), MapOperations); + } + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] + public void FecTest() + { + TestAdaptHelper.Loop(_person, src => src.Adapt(), MapOperations); + TestAdaptHelper.Loop(_foo, src => src.Adapt(), MapOperations); + TestAdaptHelper.Loop(_customer, src => src.Adapt(), MapOperations); + } + + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] + public void CodegenTest() + { + TestAdaptHelper.Loop(_person, PersonMapper.Map, MapOperations); + TestAdaptHelper.Loop(_foo, FooMapper.Map, MapOperations); + TestAdaptHelper.Loop(_customer, CustomerMapper.Map, MapOperations); + } + + [Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] + public void AutoMapperTest() + { + TestAdaptHelper.Loop(_person, src => TestAdaptHelper.AutoMapper.Map(src), MapOperations); + TestAdaptHelper.Loop(_foo, src => TestAdaptHelper.AutoMapper.Map(src), MapOperations); + TestAdaptHelper.Loop(_customer, src => TestAdaptHelper.AutoMapper.Map(src), MapOperations); + } + + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion}")] + public void FacetTest() + { + TestAdaptHelper.Loop(_person, src => new PersonFacetDto(src), MapOperations); + TestAdaptHelper.Loop(_foo, src => new FooFacetDto(src), MapOperations); + TestAdaptHelper.Loop(_customer, src => new CustomerFacetDto(src), MapOperations); + } + + [Benchmark(Description = $"Facet {TestAdaptHelper.FacetVersion} (Compiled Projection)")] + public void FacetCompiledTest() + { + TestAdaptHelper.Loop(_person, PersonFacetCompiled, MapOperations); + TestAdaptHelper.Loop(_foo, FooFacetCompiled, MapOperations); + TestAdaptHelper.Loop(_customer, CustomerFacetCompiled, MapOperations); + } + + [Benchmark(Description = $"Mapperly {TestAdaptHelper.MapperlyVersion}")] + public void MapperlyTest() + { + TestAdaptHelper.Loop(_person, MapperlyMappings.MapPerson, MapOperations); + TestAdaptHelper.Loop(_foo, MapperlyMappings.MapFoo, MapOperations); + TestAdaptHelper.Loop(_customer, MapperlyMappings.MapCustomer, MapOperations); + } + + [GlobalSetup(Target = nameof(MapsterTest))] + public void SetupMapster() + { + SetupInstances(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.Default); + _ = _person.Adapt(); + _ = _foo.Adapt(); + _ = _customer.Adapt(); + } + + [GlobalSetup(Target = nameof(RoslynTest))] + public void SetupRoslyn() + { + SetupInstances(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.Roslyn); + _ = _person.Adapt(); + _ = _foo.Adapt(); + _ = _customer.Adapt(); + } + + [GlobalSetup(Target = nameof(FecTest))] + public void SetupFec() + { + SetupInstances(); + TestAdaptHelper.UseMapsterCompiler(MapsterCompilerType.FEC); + _ = _person.Adapt(); + _ = _foo.Adapt(); + _ = _customer.Adapt(); + } + + [GlobalSetup(Target = nameof(CodegenTest))] + public void SetupCodegen() => SetupInstances(); + + [GlobalSetup(Target = nameof(AutoMapperTest))] + public void SetupAutoMapper() => SetupInstances(); + + [GlobalSetup(Target = nameof(FacetTest))] + public void SetupFacet() => SetupInstances(); + + [GlobalSetup(Target = nameof(FacetCompiledTest))] + public void SetupFacetCompiled() => SetupInstances(); + + [GlobalSetup(Target = nameof(MapperlyTest))] + public void SetupMapperly() => SetupInstances(); + + private void SetupInstances() + { + _person = TestAdaptHelper.SetupPersonInstance(); + _foo = TestAdaptHelper.SetupFooInstance(); + _customer = TestAdaptHelper.SetupCustomerInstance(); + } + } +} \ No newline at end of file diff --git a/src/Benchmark/Classes/Customer.cs b/src/Benchmark/Classes/Customer.cs index 30c832c6..d78c6ca3 100644 --- a/src/Benchmark/Classes/Customer.cs +++ b/src/Benchmark/Classes/Customer.cs @@ -1,20 +1,6 @@ namespace Mapster.Benchmark.Classes { - public class Address - { - public int Id { get; set; } - public string Street { get; set; } - public string City { get; set; } - public string Country { get; set; } - } - - public class AddressDTO - { - public int Id { get; set; } - public string City { get; set; } - public string Country { get; set; } - } - + // Nested object and collection mapping source. public class Customer { public int Id { get; set; } @@ -26,6 +12,7 @@ public class Customer public ICollection
WorkAddresses { get; set; } } + // DTO with flattening and collection shape changes. public class CustomerDTO { public int Id { get; set; } @@ -36,4 +23,19 @@ public class CustomerDTO public List WorkAddresses { get; set; } public string AddressCity { get; set; } } + + public class Address + { + public int Id { get; set; } + public string Street { get; set; } + public string City { get; set; } + public string Country { get; set; } + } + + public class AddressDTO + { + public int Id { get; set; } + public string City { get; set; } + public string Country { get; set; } + } } diff --git a/src/Benchmark/Classes/Foo.cs b/src/Benchmark/Classes/Foo.cs index f12d3edb..87c7f685 100644 --- a/src/Benchmark/Classes/Foo.cs +++ b/src/Benchmark/Classes/Foo.cs @@ -1,5 +1,6 @@ namespace Mapster.Benchmark.Classes { + // Deep recursive graph with collections. public class Foo { public string Name { get; set; } @@ -26,4 +27,32 @@ public class Foo public IEnumerable Ints { get; set; } } + + // DTO copy of Foo. + public class FooDTO + { + public string Name { get; set; } + + public int Int32 { get; set; } + + public long Int64 { get; set; } + + public int? NullInt { get; set; } + + public float Floatn { get; set; } + + public double Doublen { get; set; } + + public DateTime DateTime { get; set; } + + public FooDTO Foo1 { get; set; } + + public IEnumerable Foos { get; set; } + + public FooDTO[] FooArr { get; set; } + + public int[] IntArr { get; set; } + + public IEnumerable Ints { get; set; } + } } diff --git a/src/Benchmark/Classes/Person.cs b/src/Benchmark/Classes/Person.cs new file mode 100644 index 00000000..37173a91 --- /dev/null +++ b/src/Benchmark/Classes/Person.cs @@ -0,0 +1,28 @@ +namespace Mapster.Benchmark.Classes +{ + // FlatType POCO: no nested types, no collections. Used to exercise the "best case" + // mapping path - a simple property-to-property copy with primitive/string values. + public class Person + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public int Age { get; set; } + public DateTime BirthDate { get; set; } + public decimal Salary { get; set; } + public bool IsActive { get; set; } + } + + public class PersonDTO + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public int Age { get; set; } + public DateTime BirthDate { get; set; } + public decimal Salary { get; set; } + public bool IsActive { get; set; } + } +} diff --git a/src/Benchmark/Comparisons/FacetModels.cs b/src/Benchmark/Comparisons/FacetModels.cs index ca6330b5..d557e1e8 100644 --- a/src/Benchmark/Comparisons/FacetModels.cs +++ b/src/Benchmark/Comparisons/FacetModels.cs @@ -19,4 +19,9 @@ public partial class CustomerFacetDto [MapFrom("Address.City")] public string AddressCity { get; set; } } + + [Facet(typeof(Person))] + public partial class PersonFacetDto + { + } } \ No newline at end of file diff --git a/src/Benchmark/Comparisons/MapperlyModels.cs b/src/Benchmark/Comparisons/MapperlyModels.cs index 32fac63a..3552a8d5 100644 --- a/src/Benchmark/Comparisons/MapperlyModels.cs +++ b/src/Benchmark/Comparisons/MapperlyModels.cs @@ -45,6 +45,18 @@ public class CustomerMapperlyDto public string AddressCity { get; set; } } + public class PersonMapperlyDto + { + public int Id { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public int Age { get; set; } + public DateTime BirthDate { get; set; } + public decimal Salary { get; set; } + public bool IsActive { get; set; } + } + [Riok.Mapperly.Abstractions.Mapper(UseDeepCloning = true)] public static partial class MapperlyMappings { @@ -58,5 +70,7 @@ public static partial class MapperlyMappings [MapperIgnoreSource(nameof(Customer.Credit))] [MapProperty("Address.City", nameof(CustomerMapperlyDto.AddressCity))] public static partial CustomerMapperlyDto MapCustomer(Customer source); + + public static partial PersonMapperlyDto MapPerson(Person source); } } \ No newline at end of file diff --git a/src/Benchmark/FooMapper.g.cs b/src/Benchmark/FooMapper.g.cs index dcebe0af..0eabd97f 100644 --- a/src/Benchmark/FooMapper.g.cs +++ b/src/Benchmark/FooMapper.g.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Mapster; using Mapster.Benchmark.Classes; @@ -9,9 +9,9 @@ namespace Mapster.Benchmark { public static partial class FooMapper { - public static Foo Map(Foo p1) + public static FooDTO Map(Foo p1) { - return p1 == null ? null : new Foo() + return p1 == null ? null : new FooDTO() { Name = p1.Name, Int32 = p1.Int32, @@ -21,25 +21,25 @@ public static Foo Map(Foo p1) Doublen = p1.Doublen, DateTime = p1.DateTime, Foo1 = Map(p1.Foo1), - Foos = p1.Foos == null ? null : p1.Foos.Select(func1), + Foos = p1.Foos == null ? null : p1.Foos.Select(func1), FooArr = func2(p1.FooArr), IntArr = func3(p1.IntArr), Ints = p1.Ints == null ? null : MapsterHelper.ToEnumerable(p1.Ints) }; } - private static Foo func1(Foo p2) + private static FooDTO func1(Foo p2) { return Map(p2); } - private static Foo[] func2(Foo[] p3) + private static FooDTO[] func2(Foo[] p3) { if (p3 == null) { return null; } - Foo[] result = new Foo[p3.Length]; + FooDTO[] result = new FooDTO[p3.Length]; int v = 0; @@ -68,4 +68,4 @@ private static int[] func3(int[] p4) } } -} \ No newline at end of file +} diff --git a/src/Benchmark/FooMapper.tt b/src/Benchmark/FooMapper.tt index 003e94e1..bb1e41b3 100644 --- a/src/Benchmark/FooMapper.tt +++ b/src/Benchmark/FooMapper.tt @@ -22,8 +22,8 @@ TypeName = "FooMapper" }; var code = foo.BuildAdapter() - .CreateMapExpression() + .CreateMapExpression() .ToScript(def); - code = code.Replace("TypeAdapter.Map.Invoke", "Map"); + code = code.Replace("TypeAdapter.Map.Invoke", "Map"); WriteLine(code); #> \ No newline at end of file diff --git a/src/Benchmark/PersonMapper.g.cs b/src/Benchmark/PersonMapper.g.cs new file mode 100644 index 00000000..5060ae02 --- /dev/null +++ b/src/Benchmark/PersonMapper.g.cs @@ -0,0 +1,23 @@ +using Mapster.Benchmark.Classes; + + +namespace Mapster.Benchmark +{ + public static partial class PersonMapper + { + public static PersonDTO Map(Person p1) + { + return p1 == null ? null : new PersonDTO() + { + Id = p1.Id, + FirstName = p1.FirstName, + LastName = p1.LastName, + Email = p1.Email, + Age = p1.Age, + BirthDate = p1.BirthDate, + Salary = p1.Salary, + IsActive = p1.IsActive + }; + } + } +} \ No newline at end of file diff --git a/src/Benchmark/PersonMapper.tt b/src/Benchmark/PersonMapper.tt new file mode 100644 index 00000000..58c609ef --- /dev/null +++ b/src/Benchmark/PersonMapper.tt @@ -0,0 +1,25 @@ +<#@ template debug="true" language="C#" #> +<#@ output extension=".g.cs" #> +using Mapster.Benchmark.Classes; + + +namespace Mapster.Benchmark +{ + public static partial class PersonMapper + { + public static PersonDTO Map(Person p1) + { + return p1 == null ? null : new PersonDTO() + { + Id = p1.Id, + FirstName = p1.FirstName, + LastName = p1.LastName, + Email = p1.Email, + Age = p1.Age, + BirthDate = p1.BirthDate, + Salary = p1.Salary, + IsActive = p1.IsActive + }; + } + } +} \ No newline at end of file diff --git a/src/Benchmark/Program.cs b/src/Benchmark/Program.cs index 80c03f9a..7da13343 100644 --- a/src/Benchmark/Program.cs +++ b/src/Benchmark/Program.cs @@ -9,9 +9,10 @@ static void Main(string[] args) { var switcher = new BenchmarkSwitcher(new[] { - typeof(TestSimpleTypes), + typeof(TestFlatTypes), + typeof(TestRecursiveTypes), typeof(TestComplexTypes), - typeof(TestAll), + typeof(TestTotalAllTypes), }); switcher.Run(args, new Config()); diff --git a/src/Benchmark/TestAdaptHelper.cs b/src/Benchmark/TestAdaptHelper.cs index 3f23a6ee..c1221113 100644 --- a/src/Benchmark/TestAdaptHelper.cs +++ b/src/Benchmark/TestAdaptHelper.cs @@ -1,194 +1,123 @@ -using AutoMapper; +using AutoMapper; using FastExpressionCompiler; using Mapster.Benchmark.Classes; -using Mapster.Benchmark.Comparisons; using System.Linq.Expressions; namespace Mapster.Benchmark { - public static class TestAdaptHelper + public enum MapsterCompilerType { - private static readonly MapperConfiguration AutoMapperConfiguration = new(cfg => - { - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap() - .ForMember(destination => destination.AddressCity, - options => options.MapFrom(source => source.Address != null ? source.Address.City : null)); - }); - - private static readonly IMapper AutoMapperInstance = CreateAutoMapper(); - private static readonly Func DefaultCompiler = TypeAdapterConfig.GlobalSettings.Compiler; + Default, + Roslyn, + FEC, + } + /// + /// Minimal shared helper for the comparison benchmarks. Contains only setup data, + /// the AutoMapper instance, a Mapster compiler switch and a generic hot-loop driver. + /// + public static class TestAdaptHelper + { public const string MapsterVersion = "10.0.7"; public const string AutoMapperVersion = "14.0.0"; public const string FacetVersion = "6.5.5"; public const string MapperlyVersion = "4.3.1"; - public static Customer SetupCustomerInstance() - { - return new Customer - { - Address = new Address { City = "istanbul", Country = "turkey", Id = 1, Street = "istiklal cad." }, - HomeAddress = new Address { City = "istanbul", Country = "turkey", Id = 2, Street = "istiklal cad." }, - Id = 1, - Name = "Eduardo Najera", - Credit = 234.7m, - WorkAddresses = new List
- { - new Address {City = "istanbul", Country = "turkey", Id = 5, Street = "istiklal cad."}, - new Address {City = "izmir", Country = "turkey", Id = 6, Street = "konak"} - }, - Addresses = new[] - { - new Address {City = "istanbul", Country = "turkey", Id = 3, Street = "istiklal cad."}, - new Address {City = "izmir", Country = "turkey", Id = 4, Street = "konak"} - } - }; - } + private static readonly Func DefaultMapsterCompiler = + TypeAdapterConfig.GlobalSettings.Compiler; - public static Foo SetupFooInstance() + public static readonly IMapper AutoMapper = new MapperConfiguration(cfg => { - return new Foo + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap() + .ForMember(d => d.AddressCity, + o => o.MapFrom(s => s.Address != null ? s.Address.City : null)); + cfg.CreateMap(); + }).CreateMapper(); + + public static Foo SetupFooInstance() => new Foo + { + Name = "foo", + Int32 = 12, + Int64 = 123123, + NullInt = 16, + DateTime = DateTime.Now, + Doublen = 2312112, + Foo1 = new Foo { Name = "foo one" }, + Foos = new List { - Name = "foo", - Int32 = 12, - Int64 = 123123, - NullInt = 16, - DateTime = DateTime.Now, - Doublen = 2312112, - Foo1 = new Foo { Name = "foo one" }, - Foos = new List - { - new Foo {Name = "j1", Int64 = 123, NullInt = 321}, - new Foo {Name = "j2", Int32 = 12345, NullInt = 54321}, - new Foo {Name = "j3", Int32 = 12345, NullInt = 54321} - }, - FooArr = new[] - { - new Foo {Name = "a1"}, - new Foo {Name = "a2"}, - new Foo {Name = "a3"} - }, - IntArr = new[] { 1, 2, 3, 4, 5 }, - Ints = new[] { 7, 8, 9 } - }; - } - - private static IMapper CreateAutoMapper() - { - AutoMapperConfiguration.AssertConfigurationIsValid(); - AutoMapperConfiguration.CompileMappings(); - return AutoMapperConfiguration.CreateMapper(); - } - - private static void SetupCompiler(MapsterCompilerType type) + new Foo { Name = "j1", Int64 = 123, NullInt = 321 }, + new Foo { Name = "j2", Int32 = 12345, NullInt = 54321 }, + new Foo { Name = "j3", Int32 = 12345, NullInt = 54321 }, + }, + FooArr = new[] + { + new Foo { Name = "a1" }, + new Foo { Name = "a2" }, + new Foo { Name = "a3" }, + }, + IntArr = new[] { 1, 2, 3, 4, 5 }, + Ints = new[] { 7, 8, 9 }, + }; + + public static Customer SetupCustomerInstance() => new Customer + { + Id = 1, + Name = "Eduardo Najera", + Credit = 234.7m, + Address = new Address { Id = 1, City = "istanbul", Country = "turkey", Street = "istiklal cad." }, + HomeAddress = new Address { Id = 2, City = "istanbul", Country = "turkey", Street = "istiklal cad." }, + Addresses = new[] + { + new Address { Id = 3, City = "istanbul", Country = "turkey", Street = "istiklal cad." }, + new Address { Id = 4, City = "izmir", Country = "turkey", Street = "konak" }, + }, + WorkAddresses = new List
+ { + new Address { Id = 5, City = "istanbul", Country = "turkey", Street = "istiklal cad." }, + new Address { Id = 6, City = "izmir", Country = "turkey", Street = "konak" }, + }, + }; + + public static Person SetupPersonInstance() => new Person + { + Id = 42, + FirstName = "Eduardo", + LastName = "Najera", + Email = "eduardo@example.com", + Age = 39, + BirthDate = new DateTime(1986, 7, 11), + Salary = 12345.67m, + IsActive = true, + }; + + /// + /// Switches Mapster's global expression compiler. Call this from a [GlobalSetup] before warming up the mapping. + /// + public static void UseMapsterCompiler(MapsterCompilerType type) { TypeAdapterConfig.GlobalSettings.Compiler = type switch { - MapsterCompilerType.Default => DefaultCompiler, - MapsterCompilerType.Roslyn => expression => expression.CompileWithDebugInfo(), - MapsterCompilerType.FEC => expression => expression.CompileFast(), + MapsterCompilerType.Default => DefaultMapsterCompiler, + MapsterCompilerType.Roslyn => e => e.CompileWithDebugInfo(), + MapsterCompilerType.FEC => e => e.CompileFast(), _ => throw new ArgumentOutOfRangeException(nameof(type)), }; } - public static void ConfigureMapster(TSource sourceInstance, MapsterCompilerType type) - where TSource : class - where TDestination : class - { - SetupCompiler(type); - TypeAdapterConfig.GlobalSettings.Compile(typeof(TSource), typeof(TDestination)); - _ = sourceInstance.Adapt(); - } - - public static void ConfigureAutoMapper(TSource sourceInstance) - where TSource : class - { - _ = AutoMapperInstance.Map(sourceInstance); - } - - public static void ConfigureFacet(Foo sourceInstance) + /// + /// Hot loop: invokes on times. + /// Keeps the last result alive so the JIT can't dead-code-eliminate the call. + /// + public static void Loop(TSrc src, Func map, int count) { - _ = new FooFacetDto(sourceInstance); - } + TDest r = default!; + for (var i = 0; i < count; i++) + r = map(src); - public static void ConfigureFacet(Customer sourceInstance) - { - _ = new CustomerFacetDto(sourceInstance); - } - - public static void ConfigureMapperly(Foo sourceInstance) - { - _ = MapperlyMappings.MapFoo(sourceInstance); + GC.KeepAlive(r); } - - public static void ConfigureMapperly(Customer sourceInstance) - { - _ = MapperlyMappings.MapCustomer(sourceInstance); - } - - public static void TestMapsterAdapter(TSrc item, int mapOperations) - where TSrc : class - where TDest : class, new() - { - Loop(item, source => source.Adapt(), mapOperations); - } - - public static void TestAutoMapper(TSrc item, int mapOperations) - where TSrc : class - where TDest : class, new() - { - Loop(item, source => AutoMapperInstance.Map(source), mapOperations); - } - - public static void TestFacet(Foo item, int mapOperations) - { - Loop(item, source => new FooFacetDto(source), mapOperations); - } - - public static void TestFacet(Customer item, int mapOperations) - { - Loop(item, source => new CustomerFacetDto(source), mapOperations); - } - - public static void TestMapperly(Foo item, int mapOperations) - { - Loop(item, MapperlyMappings.MapFoo, mapOperations); - } - - public static void TestMapperly(Customer item, int mapOperations) - { - Loop(item, MapperlyMappings.MapCustomer, mapOperations); - } - - public static void TestCodeGen(Foo item, int mapOperations) - { - Loop(item, FooMapper.Map, mapOperations); - } - - public static void TestCodeGen(Customer item, int mapOperations) - { - Loop(item, CustomerMapper.Map, mapOperations); - } - - private static void Loop(TSource item, Func map, int mapOperations) - { - TDestination result = default!; - for (var i = 0; i < mapOperations; i++) - { - result = map(item); - } - - GC.KeepAlive(result); - } - } - - public enum MapsterCompilerType - { - Default, - Roslyn, - FEC, } -} \ No newline at end of file +} From c89a232c576192c64ed3cf66af31eadbdefeda3e Mon Sep 17 00:00:00 2001 From: Qyperion Date: Sat, 16 May 2026 00:56:03 +0300 Subject: [PATCH 4/5] docs: add benchmark docs page and actualize results --- README.md | 24 +++++---- docs/articles/_Sidebar.md | 5 ++ docs/articles/benchmarks.md | 103 ++++++++++++++++++++++++++++++++++++ docs/articles/toc.yml | 4 +- 4 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 docs/articles/benchmarks.md diff --git a/README.md b/README.md index 5f9a107a..ea8b65c3 100644 --- a/README.md +++ b/README.md @@ -149,15 +149,21 @@ Mapster was designed to be efficient on both speed and memory. The repository in - Facet - Mapperly -| Method | MapOperations | Mean | StdDev | Error | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | -| -------- | -------------- | -----: | -------: | ------: | ------: | -----: | -----: | ----------: | ----------: | -| `Mapster 10.0.7` | 1000000 | 412,534 us | 2,704 us | 4,543 us | 1.00 | 77000 | - | 1243.59 MB | 1.00 | -| `Mapster 10.0.7 (Roslyn)` | 1000000 | 397,028 us | 5,174 us | 8,695 us | 0.96 | 75000 | - | 1205.44 MB | 0.97 | -| `Mapster 10.0.7 (FEC)` | 1000000 | 124,374 us | 1,290 us | 2,466 us | 0.30 | 74000 | - | 1182.56 MB | 0.95 | -| `Mapster 10.0.7 (Codegen)` | 1000000 | 105,214 us | 1,312 us | 2,206 us | 0.26 | 75500 | 166 | 1205.44 MB | 0.97 | -| `AutoMapper 14.0.0` | 1000000 | 600,077 us | 63,170 us | 95,505 us | 1.45 | 197000 | 1000 | 3158.59 MB | 2.54 | -| `Facet 6.5.5` | 1000000 | 628,280 us | 7,326 us | 11,076 us | 1.52 | 325000 | 1000 | 5187.99 MB | 4.17 | -| `Mapperly 4.3.1` | 1000000 | 128,521 us | 1,453 us | 2,442 us | 0.31 | 94500 | 250 | 1510.62 MB | 1.21 | +The snapshot below shows the `FlatTypes` scenario (`Person -> PersonDTO`), a best-case DTO with simple property-to-property mapping and no nested objects or collections. + +> [!NOTE] +> More complex object shapes can change the relative results. See the [complete benchmark results](https://mapstermapper.github.io/Mapster/articles/benchmarks.html) for `ComplexTypes`, `RecursiveTypes`, and `TotalAllTypes`. + +| Method | MapOperations | Mean | StdDev | Error | Ns/Map | Ratio | Gen0 | Allocated | Alloc Ratio | Bytes/Map | +|------------------------------------ |-------------- |----------:|----------:|----------:|-------:|------:|-----:|----------:|------------:|----------:| +| `Mapster 10.0.7` | 1000000 | 6.849 ms | 0.6851 ms | 1.0358 ms | 6.849 | 1.01 | 4781 | 76.29 MB | 1.00 | 80 | +| `Mapster 10.0.7 (Roslyn)` | 1000000 | 6.579 ms | 0.2782 ms | 0.4206 ms | 6.579 | 0.97 | 4781 | 76.29 MB | 1.00 | 80 | +| `Mapster 10.0.7 (FEC)` | 1000000 | 6.549 ms | 0.9130 ms | 1.3803 ms | 6.549 | 0.97 | 4781 | 76.29 MB | 1.00 | 80 | +| `Mapster 10.0.7 (Codegen)` | 1000000 | 5.868 ms | 0.3266 ms | 0.5488 ms | 5.868 | 0.86 | 4781 | 76.29 MB | 1.00 | 80 | +| `AutoMapper 14.0.0` | 1000000 | 29.645 ms | 0.8963 ms | 1.5062 ms | 29.645 | 4.37 | 4750 | 76.29 MB | 1.00 | 80 | +| `Facet 6.5.5` | 1000000 | 7.801 ms | 1.0231 ms | 1.5467 ms | 7.801 | 1.15 | 8601 | 137.33 MB | 1.80 | 144 | +| `Facet 6.5.5 (Compiled Projection)` | 1000000 | 5.508 ms | 0.7064 ms | 1.0679 ms | 5.508 | 0.81 | 4781 | 76.29 MB | 1.00 | 80 | +| `Mapperly 4.3.1` | 1000000 | 6.521 ms | 0.8369 ms | 1.2652 ms | 6.521 | 0.96 | 4781 | 76.29 MB | 1.00 | 80 | ### Step into debugging diff --git a/docs/articles/_Sidebar.md b/docs/articles/_Sidebar.md index 22c58675..dec82b38 100644 --- a/docs/articles/_Sidebar.md +++ b/docs/articles/_Sidebar.md @@ -54,3 +54,8 @@ * [Fluent API](https://github.com/MapsterMapper/Mapster/wiki/Fluent-API-Code-generation) * [Attributes](https://github.com/MapsterMapper/Mapster/wiki/Attribute-base-Code-generation) * [Interfaces](https://github.com/MapsterMapper/Mapster/wiki/Interface-base-Code-generation) + +## Benchmarks + +* [Benchmark results](https://mapstermapper.github.io/Mapster/articles/benchmarks.html) +* \ No newline at end of file diff --git a/docs/articles/benchmarks.md b/docs/articles/benchmarks.md new file mode 100644 index 00000000..7d524544 --- /dev/null +++ b/docs/articles/benchmarks.md @@ -0,0 +1,103 @@ +--- +uid: Mapster.Benchmarks +title: "Benchmark results" +--- + +Mapster includes a benchmark project in [`src/Benchmark`](https://github.com/MapsterMapper/Mapster/tree/master/src/Benchmark) that compares the local Mapster build with AutoMapper, Facet, and Mapperly across several object shapes. + +This page is a May 2026 snapshot of those benchmarks. Treat the numbers as a comparison point for this environment and benchmark configuration rather than an absolute guarantee for every machine or application. + +## How to read the tables + +- `Mean` is the total time for one BenchmarkDotNet benchmark invocation. Each invocation runs a manual hot loop with `MapOperations = 1,000,000`. +- `Ns/Map` normalizes `Mean` to the approximate cost of one logical mapping call. +- `Allocated` is the memory allocated by one benchmark invocation. +- `Bytes/Map` normalizes `Allocated` to one logical mapping call. +- `Ratio` and `Alloc Ratio` are relative to the default Mapster benchmark in the same scenario. +- `TotalAllTypes` runs three mapping scenarios in one benchmark invocation, so its `Ns/Map` and `Bytes/Map` values are divided by `MapOperations * 3`. + +## Benchmark scenarios + +### FlatTypes + +`FlatTypes` maps `Person -> PersonDTO`. It is a flat DTO shape: simple property-to-property copy, no nested objects, and no collections. This scenario mostly highlights mapper call overhead, generated IL quality, and allocation rate for a best-case DTO. + +### ComplexTypes + +`ComplexTypes` maps `Customer -> CustomerDTO`. It includes nested address mapping, array/list shape changes, and a flattening rule (`AddressCity <- Address.City`). This scenario is useful for typical DTOs that combine nested objects, collections, and a small amount of custom member mapping. + +### RecursiveTypes + +`RecursiveTypes` maps `Foo -> FooDTO`. The type shape is self-recursive: a `Foo` can contain another `Foo`, an enumerable of `Foo`, and an array of `Foo`. The sample data does not intentionally create a back-reference cycle, but the mapping graph is deeper and allocates more nested DTOs than the flat or customer scenarios. + +### TotalAllTypes + +`TotalAllTypes` runs `FlatTypes`, `RecursiveTypes`, and `ComplexTypes` sequentially in one benchmark method. `Mean` is therefore the total batch time for all three scenarios; use `Ns/Map` and `Bytes/Map` for normalized per-map interpretation. + +## Compared methods + +- `Mapster` uses the default Mapster expression compiler. +- `Mapster (Roslyn)` uses the Roslyn/debug-info compiler path. +- `Mapster (FEC)` uses FastExpressionCompiler. +- `Mapster (Codegen)` uses generated mapping code. +- `AutoMapper`, `Facet`, and `Mapperly` are included as external comparison points. +- `Facet (Compiled Projection)` uses Facet's compiled projection path, which can behave very differently from the constructor path depending on the object shape. + +## Results + +### FlatTypes + +| Scenario | Method | MapOperations | Mean | StdDev | Error | Ns/Map | Ratio | Gen0 | Allocated | Alloc Ratio | Bytes/Map | +|---------- |------------------------------------ |-------------- |----------:|----------:|----------:|-------:|------:|-----:|----------:|------------:|----------:| +| FlatTypes | `Mapster 10.0.7` | 1000000 | 6.849 ms | 0.6851 ms | 1.0358 ms | 6.849 | 1.01 | 4781 | 76.29 MB | 1.00 | 80 | +| FlatTypes | `Mapster 10.0.7 (Roslyn)` | 1000000 | 6.579 ms | 0.2782 ms | 0.4206 ms | 6.579 | 0.97 | 4781 | 76.29 MB | 1.00 | 80 | +| FlatTypes | `Mapster 10.0.7 (FEC)` | 1000000 | 6.549 ms | 0.9130 ms | 1.3803 ms | 6.549 | 0.97 | 4781 | 76.29 MB | 1.00 | 80 | +| FlatTypes | `Mapster 10.0.7 (Codegen)` | 1000000 | 5.868 ms | 0.3266 ms | 0.5488 ms | 5.868 | 0.86 | 4781 | 76.29 MB | 1.00 | 80 | +| FlatTypes | `AutoMapper 14.0.0` | 1000000 | 29.645 ms | 0.8963 ms | 1.5062 ms | 29.645 | 4.37 | 4750 | 76.29 MB | 1.00 | 80 | +| FlatTypes | `Facet 6.5.5` | 1000000 | 7.801 ms | 1.0231 ms | 1.5467 ms | 7.801 | 1.15 | 8601 | 137.33 MB | 1.80 | 144 | +| FlatTypes | `Facet 6.5.5 (Compiled Projection)` | 1000000 | 5.508 ms | 0.7064 ms | 1.0679 ms | 5.508 | 0.81 | 4781 | 76.29 MB | 1.00 | 80 | +| FlatTypes | `Mapperly 4.3.1` | 1000000 | 6.521 ms | 0.8369 ms | 1.2652 ms | 6.521 | 0.96 | 4781 | 76.29 MB | 1.00 | 80 | + +### ComplexTypes + +| Scenario | Method | MapOperations | Mean | StdDev | Error | Ns/Map | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | Bytes/Map | +|------------- |------------------------------------ |-------------- |----------:|----------:|----------:|--------:|------:|-------:|-----:|-----------:|------------:|----------:| +| ComplexTypes | `Mapster 10.0.7` | 1000000 | 78.59 ms | 1.519 ms | 2.553 ms | 78.586 | 1.00 | 28111 | - | 450.13 MB | 1.00 | 472 | +| ComplexTypes | `Mapster 10.0.7 (Roslyn)` | 1000000 | 53.48 ms | 1.760 ms | 2.958 ms | 53.475 | 0.68 | 25818 | - | 411.99 MB | 0.92 | 432 | +| ComplexTypes | `Mapster 10.0.7 (FEC)` | 1000000 | 69.29 ms | 1.539 ms | 2.326 ms | 69.288 | 0.88 | 28200 | - | 450.13 MB | 1.00 | 472 | +| ComplexTypes | `Mapster 10.0.7 (Codegen)` | 1000000 | 51.86 ms | 2.514 ms | 3.801 ms | 51.863 | 0.66 | 25750 | - | 411.99 MB | 0.92 | 432 | +| ComplexTypes | `AutoMapper 14.0.0` | 1000000 | 112.27 ms | 4.516 ms | 7.590 ms | 112.270 | 1.43 | 29142 | - | 465.39 MB | 1.03 | 488 | +| ComplexTypes | `Facet 6.5.5` | 1000000 | 446.72 ms | 51.706 ms | 78.172 ms | 446.725 | 5.69 | 175000 | 500 | 2792.36 MB | 6.20 | 2928 | +| ComplexTypes | `Facet 6.5.5 (Compiled Projection)` | 1000000 | 678.87 ms | 42.646 ms | 64.474 ms | 678.868 | 8.64 | 44000 | - | 709.53 MB | 1.58 | 744 | +| ComplexTypes | `Mapperly 4.3.1` | 1000000 | 52.81 ms | 1.134 ms | 1.714 ms | 52.812 | 0.67 | 25769 | - | 411.99 MB | 0.92 | 432 | + +### RecursiveTypes + +| Scenario | Method | MapOperations | Mean | StdDev | Error | Ns/Map | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | Bytes/Map | +|--------------- |------------------------------------ |-------------- |----------:|----------:|----------:|--------:|------:|-------:|-----:|-----------:|------------:|----------:| +| RecursiveTypes | `Mapster 10.0.7` | 1000000 | 404.49 ms | 63.593 ms | 96.14 ms | 404.486 | 1.02 | 49000 | - | 793.46 MB | 1.00 | 832 | +| RecursiveTypes | `Mapster 10.0.7 (Roslyn)` | 1000000 | 383.00 ms | 34.563 ms | 52.25 ms | 382.999 | 0.97 | 49000 | - | 793.46 MB | 1.00 | 832 | +| RecursiveTypes | `Mapster 10.0.7 (FEC)` | 1000000 | 84.82 ms | 10.777 ms | 16.29 ms | 84.816 | 0.21 | 45875 | 125 | 732.42 MB | 0.92 | 768 | +| RecursiveTypes | `Mapster 10.0.7 (Codegen)` | 1000000 | 82.81 ms | 11.104 ms | 16.79 ms | 82.806 | 0.21 | 49625 | 125 | 793.46 MB | 1.00 | 832 | +| RecursiveTypes | `AutoMapper 14.0.0` | 1000000 | 585.42 ms | 88.660 ms | 134.04 ms | 585.425 | 1.48 | 168000 | 1000 | 2693.18 MB | 3.39 | 2824 | +| RecursiveTypes | `Facet 6.5.5` | 1000000 | 302.24 ms | 9.444 ms | 18.06 ms | 302.241 | 0.76 | 150000 | 666 | 2395.63 MB | 3.02 | 2512 | +| RecursiveTypes | `Facet 6.5.5 (Compiled Projection)` | 1000000 | 708.77 ms | 58.207 ms | 88.00 ms | 708.770 | 1.79 | 73000 | - | 1174.93 MB | 1.48 | 1232 | +| RecursiveTypes | `Mapperly 4.3.1` | 1000000 | 116.05 ms | 16.639 ms | 25.16 ms | 116.055 | 0.29 | 68833 | 166 | 1098.63 MB | 1.38 | 1152 | + +### TotalAllTypes + +| Scenario | Method | MapOperations | Mean | StdDev | Error | Ns/Map | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio | Bytes/Map | +|-------------- |------------------------------------ |-------------- |-----------:|---------:|----------:|-------:|------:|-------:|-----:|----------:|------------:|----------:| +| TotalAllTypes | `Mapster 10.0.7` | 1000000 | 413.9 ms | 16.88 ms | 25.52 ms | 137.97 | 1.00 | 82000 | - | 1.29 GB | 1.00 | 461 | +| TotalAllTypes | `Mapster 10.0.7 (Roslyn)` | 1000000 | 546.4 ms | 29.01 ms | 43.86 ms | 182.12 | 1.32 | 80000 | - | 1.25 GB | 0.97 | 448 | +| TotalAllTypes | `Mapster 10.0.7 (FEC)` | 1000000 | 155.7 ms | 24.48 ms | 37.01 ms | 51.91 | 0.38 | 78800 | - | 1.23 GB | 0.95 | 440 | +| TotalAllTypes | `Mapster 10.0.7 (Codegen)` | 1000000 | 117.4 ms | 8.56 ms | 12.94 ms | 39.14 | 0.28 | 80250 | - | 1.25 GB | 0.97 | 448 | +| TotalAllTypes | `AutoMapper 14.0.0` | 1000000 | 681.4 ms | 76.25 ms | 115.27 ms | 227.13 | 1.65 | 202000 | 1000 | 3.16 GB | 2.45 | 1130 | +| TotalAllTypes | `Facet 6.5.5` | 1000000 | 677.4 ms | 33.30 ms | 55.96 ms | 225.80 | 1.64 | 329000 | 1000 | 5.14 GB | 3.99 | 1840 | +| TotalAllTypes | `Facet 6.5.5 (Compiled Projection)` | 1000000 | 1,337.4 ms | 42.95 ms | 64.94 ms | 445.81 | 3.24 | 122000 | - | 1.91 GB | 1.49 | 685 | +| TotalAllTypes | `Mapperly 4.3.1` | 1000000 | 141.5 ms | 3.04 ms | 5.81 ms | 47.10 | 0.34 | 99250 | 250 | 1.55 GB | 1.20 | 554 | + +## Interpretation notes + +- The fastest method depends on the object shape. Flat DTOs mostly measure low-level call overhead; recursive graphs and collection-heavy DTOs shift the bottleneck toward nested object creation and collection mapping. +- `Facet` constructor and compiled-projection paths are shown separately because they exercise different APIs and can trade CPU time for allocation behavior differently across scenarios. diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml index 245cfb26..d218f825 100644 --- a/docs/articles/toc.yml +++ b/docs/articles/toc.yml @@ -15,4 +15,6 @@ items: topicHref: xref:Mapster.Packages.Async - name: Tools href: tools/toc.yml - topicHref: xref:Mapster.Tools.MapsterTool.Overview \ No newline at end of file + topicHref: xref:Mapster.Tools.MapsterTool.Overview +- name: Benchmarks + href: benchmarks.md \ No newline at end of file From 0562073dfd832d86acb0141458066351651ee1ae Mon Sep 17 00:00:00 2001 From: Qyperion Date: Sun, 17 May 2026 12:33:25 +0300 Subject: [PATCH 5/5] fix(docs): replace Benchmark project link in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea8b65c3..c73ff524 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ public static class StudentMapper { ### Performance & Memory efficient -Mapster was designed to be efficient on both speed and memory. The repository includes a benchmark project in [`src/Benchmark`](src/Benchmark) that compares the local Mapster build, its compiler variants, and other modern mapping libraries like AutoMapper, Mapperly, and Facet. +Mapster was designed to be efficient on both speed and memory. The repository includes a benchmark project in [`src/Benchmark`](https://github.com/MapsterMapper/Mapster/tree/master/src/Benchmark) that compares the local Mapster build, its compiler variants, and other modern mapping libraries like AutoMapper, Mapperly, and Facet. - [Roslyn Compiler](https://mapstermapper.github.io/Mapster/articles/packages/ExpressionDebugging.html) - [FEC](https://mapstermapper.github.io/Mapster/articles/packages/FastExpressionCompiler.html)