-
Notifications
You must be signed in to change notification settings - Fork 0
Performance
ZMapper is designed for near-zero overhead mapping. All mapping code is generated at compile time, resulting in performance that matches or beats hand-written mapping code.
All benchmarks were run using BenchmarkDotNet and compare ZMapper against:
- Manual Mapping — hand-written property assignments (baseline)
- Mapperly — another source-generation mapper
- AutoMapper — reflection-based mapper (industry standard)
| Method | Mean | Ratio | Allocated |
|---|---|---|---|
| ZMapper | 16.56 ns | 0.93x | 88 B |
| Manual Mapping | 17.79 ns | 1.00x | 88 B |
| Mapperly | 17.83 ns | 1.00x | 88 B |
| AutoMapper | 52.64 ns | 2.96x | 88 B |
ZMapper matches manual mapping speed and is 3x faster than AutoMapper.
| Method | Mean | Ratio |
|---|---|---|
| Manual Loop | 18.79 us | 1.00x |
| ZMapper (Span) | 19.38 us | 1.03x |
| Mapperly Loop | 23.50 us | 1.25x |
| AutoMapper Loop | 54.38 us | 2.89x |
Span-based batch mapping is nearly identical to hand-written loops.
| Method | Mean | Ratio |
|---|---|---|
| Manual (Order) | 168.35 ns | 1.00x |
| ZMapper (Order) | 172.59 ns | 1.03x |
| Mapperly (Order) | 214.79 ns | 1.28x |
| AutoMapper (Order) | 351.79 ns | 2.09x |
Even with deep object graphs (Order -> OrderItems[] -> OrderStatusInfo), ZMapper stays within 3% of manual mapping.
| Method | Mean | Ratio |
|---|---|---|
| ZMapper (Span) | 122.75 us | 0.86x |
| Manual Loop | 142.93 us | 1.00x |
| Mapperly Loop | 167.91 us | 1.18x |
| AutoMapper Loop | 237.94 us | 1.67x |
ZMapper's Span-based batch mapping is faster than manual mapping for complex objects.
ZMapper's Roslyn source generator analyzes your mapping configuration at build time and generates optimized C# code. At runtime, there is:
- No reflection
- No dictionary lookups
- No dynamic dispatch
- No expression tree compilation
The generated code uses direct property assignments:
// What gets generated (simplified)
destination.UserName = source.Username;
destination.EmailAddress = source.Email;
destination.Active = source.IsActive;This compiles to the same IL as hand-written code.
Span-based iteration avoids:
- Heap allocation for the enumerator object
- Interface dispatch overhead (
IEnumerator.MoveNext()) - Bounds checking (the JIT can optimize Span iteration)
// Generated batch mapping (simplified)
public Person[] MapArray(ReadOnlySpan<PersonDto> source)
{
var result = new Person[source.Length];
for (int i = 0; i < source.Length; i++)
{
result[i] = Map(source[i]);
}
return result;
}Generated extension methods are marked with [MethodImpl(MethodImplOptions.AggressiveInlining)], instructing the JIT compiler to inline the method body at call sites:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static User ToUser(this UserDto source) { ... }Value types (int, DateTime, enums, etc.) are never boxed. The generated code uses concrete types throughout, keeping value types on the stack.
Run the benchmark suite yourself:
cd tests/ZMapper.Benchmarks
dotnet run -c ReleaseFilter specific benchmarks:
# Only complex object benchmarks
dotnet run -c Release -- --filter *Complex*
# Only batch/collection benchmarks
dotnet run -c Release -- --filter *Collection*
# Only simple mapping benchmarks
dotnet run -c Release -- --filter *Simple*-
Use
MapArraywith Span for batch operations — it's the fastest path -
Use extension methods (
.ToUser()) for single objects — they're JIT-inlined - Register mappings from leaf to root for nested objects
-
Avoid unnecessary hooks (
BeforeMap/AfterMap) when simpleForMembersuffices — hooks add a delegate invocation -
Use
IgnoreNonExisting()instead of many individual.Ignore()calls when most properties don't match
- Architecture — How the source generator works under the hood
- Collections and Batch Mapping — Batch mapping strategies
- API Reference — Complete method reference
ZMapper v1.2.1 | GitHub Repository | Report an Issue | MIT License