Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
Qyperion marked this conversation as resolved.
Outdated

- [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 |
Comment thread
DocSvartz marked this conversation as resolved.
Outdated
| `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

Expand Down
10 changes: 3 additions & 7 deletions docs/articles/packages/ExpressionDebugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,8 @@ TypeAdapterConfig.GlobalSettings.Compiler = exp => exp.CompileWithDebugInfo(opt)
var dto = poco.Adapt<SimplePoco, SimpleDto>(); //<-- 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.
12 changes: 7 additions & 5 deletions docs/articles/packages/FastExpressionCompiler.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
6 changes: 5 additions & 1 deletion src/.editorconfig
Original file line number Diff line number Diff line change
@@ -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
Expand Down
38 changes: 21 additions & 17 deletions src/Benchmark.Development/Benchmark.Development.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>12.0</LangVersion>

<Nullable>enable</Nullable>
<SignAssembly>True</SignAssembly>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Mapster.Benchmark.Development</RootNamespace>

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AssemblyOriginatorKeyFile>Benchmark.Development.snk</AssemblyOriginatorKeyFile>
<SignAssembly>True</SignAssembly>
<DelaySign>False</DelaySign>
<SciVersion Condition="'$(SciVersion)' == ''">7.4.0</SciVersion>
<LangVersion>12.0</LangVersion>

<SciVersion Condition="'$(SciVersion)' == ''">7.4.0</SciVersion>
</PropertyGroup>

<ItemGroup>
Expand All @@ -21,17 +25,17 @@
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Mapster" Version="$(SciVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Release'">
<PackageReference Include="Mapster" Version="$(SciVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='nuget-bench'">
<PackageReference Include="Mapster" Version="$(SciVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='developer-bench'">
<ProjectReference Include="..\Mapster\Mapster.csproj" />
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Mapster" Version="$(SciVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Release'">
<PackageReference Include="Mapster" Version="$(SciVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='nuget-bench'">
<PackageReference Include="Mapster" Version="$(SciVersion)" />
</ItemGroup>

<ItemGroup Condition="'$(Configuration)'=='developer-bench'">
<ProjectReference Include="..\Mapster\Mapster.csproj" />
</ItemGroup>

</Project>
8 changes: 3 additions & 5 deletions src/Benchmark.Development/Benchmarks/Config.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -32,7 +30,7 @@ public Config()

AddColumn(BaselineRatioColumn.RatioMean);
AddColumnProvider(DefaultColumnProviders.Metrics);



foreach (var version in MapsterVersion.Get())
Expand Down
6 changes: 3 additions & 3 deletions src/Benchmark.Development/Benchmarks/TestAll.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down
8 changes: 4 additions & 4 deletions src/Benchmark.Development/Benchmarks/TestComplexTypes.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -15,7 +15,7 @@ public void MapsterTest()
{
TestAdaptHelper.TestMapsterAdapter<Customer, CustomerDTO>(_customerInstance, Iterations);
}

[GlobalSetup(Target = nameof(MapsterTest))]
public void SetupMapster()
{
Expand Down
8 changes: 4 additions & 4 deletions src/Benchmark.Development/Benchmarks/TestSimpleTypes.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -15,7 +15,7 @@ public void MapsterTest()
{
TestAdaptHelper.TestMapsterAdapter<Foo, Foo>(_fooInstance, Iterations);
}

[GlobalSetup(Target = nameof(MapsterTest))]
public void SetupMapster()
{
Expand Down
4 changes: 1 addition & 3 deletions src/Benchmark.Development/Classes/Customer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Collections.Generic;

namespace Benchmark.Classes
namespace Mapster.Benchmark.Development.Classes
{
public class Address
{
Expand Down
5 changes: 1 addition & 4 deletions src/Benchmark.Development/Classes/Foo.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;

namespace Benchmark.Classes
namespace Mapster.Benchmark.Development.Classes
{
public class Foo
{
Expand Down
4 changes: 2 additions & 2 deletions src/Benchmark.Development/MapsterVersion.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
namespace Benchmark.Development
namespace Mapster.Benchmark.Development
{
internal static class MapsterVersion
{

internal static string[] Get() =>
[
"7.4.0",
"9.0.0-pre01"
"10.0.0"
];
}
}
4 changes: 2 additions & 2 deletions src/Benchmark.Development/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Benchmark.Benchmarks;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Running;
using Mapster.Benchmark.Development.Benchmarks;

var switcher = new BenchmarkSwitcher(new[]
{
Expand Down
15 changes: 7 additions & 8 deletions src/Benchmark.Development/TestAdaptHelper.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using Benchmark.Classes;
using Mapster;
using Mapster.Benchmark.Development.Classes;
Comment thread
Qyperion marked this conversation as resolved.
using System.Linq.Expressions;

namespace Benchmark
namespace Mapster.Benchmark.Development
{
public static class TestAdaptHelper
{

public static Customer SetupCustomerInstance()
{
return new Customer
Expand Down Expand Up @@ -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)),
};
}
Expand All @@ -75,14 +74,14 @@ public static void ConfigureMapster(Foo fooInstance, MapsterCompilerType type)
TypeAdapterConfig.GlobalSettings.Compile(typeof(Foo), typeof(Foo)); //recompile
fooInstance.Adapt<Foo, Foo>(); //exercise
}

public static void ConfigureMapster(Customer customerInstance, MapsterCompilerType type)
{
SetupCompiler(type);
TypeAdapterConfig.GlobalSettings.Compile(typeof(Customer), typeof(CustomerDTO)); //recompile
customerInstance.Adapt<Customer, CustomerDTO>(); //exercise
}

public static void TestMapsterAdapter<TSrc, TDest>(TSrc item, int iterations)
where TSrc : class
where TDest : class, new()
Expand Down
97 changes: 53 additions & 44 deletions src/Benchmark/Benchmark.csproj
Original file line number Diff line number Diff line change
@@ -1,45 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<CopyLocalLockFileAssemblies Condition=" '$(Configuration)'=='Debug' ">true</CopyLocalLockFileAssemblies>
<DefaultItemExcludes>**/*.g.cs</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<Compile Include="CustomerMapper.g.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>CustomerMapper.tt</DependentUpon>
</Compile>
<Compile Include="FooMapper.g.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FooMapper.tt</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="ExpressionDebugger" Version="2.2.1" />
<PackageReference Include="ExpressionTranslator" Version="2.5.0" />
<PackageReference Include="FastExpressionCompiler" Version="5.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ExpressionDebugger\ExpressionDebugger.csproj" />
<ProjectReference Include="..\ExpressionTranslator\ExpressionTranslator.csproj" />
<ProjectReference Include="..\Mapster\Mapster.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="CustomerMapper.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>CustomerMapper.g.cs</LastGenOutput>
</None>
<None Update="FooMapper.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FooMapper.g.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>

<RootNamespace>Mapster.Benchmark</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>

<CopyLocalLockFileAssemblies Condition=" '$(Configuration)'=='Debug' ">true</CopyLocalLockFileAssemblies>
<DefaultItemExcludes>**/*.g.cs</DefaultItemExcludes>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="AutoMapper" Version="14.0.0" />
Comment thread
DocSvartz marked this conversation as resolved.
Outdated
<PackageReference Include="Facet" Version="6.5.5" />
<PackageReference Include="FastExpressionCompiler" Version="5.4.1" />
<PackageReference Include="Riok.Mapperly" Version="4.3.1" ExcludeAssets="runtime" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ExpressionDebugger\ExpressionDebugger.csproj" />
<ProjectReference Include="..\ExpressionTranslator\ExpressionTranslator.csproj" />
<ProjectReference Include="..\Mapster\Mapster.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Include="CustomerMapper.g.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>CustomerMapper.tt</DependentUpon>
</Compile>
<Compile Include="FooMapper.g.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FooMapper.tt</DependentUpon>
</Compile>
</ItemGroup>

<ItemGroup>
<None Update="CustomerMapper.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>CustomerMapper.g.cs</LastGenOutput>
</None>
<None Update="FooMapper.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FooMapper.g.cs</LastGenOutput>
</None>
</ItemGroup>

<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
</Project>
Loading
Loading