-
Notifications
You must be signed in to change notification settings - Fork 0
Merlin Vs Dapper
Dapper is around 20% to 30% faster than Merlin, which is to be expected given the amount of resources went into making Dapper.
Our point we are making is with only marginal performance hit you can use a much simpler api, and bonus the models made for Merlin can also be used for Dapper. Our benchmark uses the exact same model and exact same query so if you have 1 or 2 queries that pulls 1 million + records you could substitute Dapper for when you need that extra 160ms
The results are below.
- Runtime: .NET 10.0.10
- CPU: Intel Core i7-8086K @ 4.00GHz
- OS: Windows 11
- Dataset: 100,000 rows
- Database: MySQL
- Benchmark: BenchmarkDotNet
- GC: Concurrent Workstation
Scenario: 100,000 User (11x Properties) records with no nested objects
| Metric | Dapper | Merlin | Difference |
|---|---|---|---|
| Mean Execution Time | 349.5 ms | 422.6 ms | +73.1 ms |
| Relative Performance | 1.00x | 1.21x | 21% slower |
| Allocated Memory | 169.72 MB | 190.32 MB | +20.60 MB |
| Allocation Ratio | 1.00x | 1.12x | 12% more |
| Gen0 Collections | 29,000 | 34,000 | +17% |
| Gen1 Collections | 9,000 | 10,000 | +11% |
| Gen2 Collections | 2,000 | 3,000 | +50% |
Merlin introduced approximately:
- 73 ms additional execution time
- 20 MB additional allocations
- ~21% execution overhead
- ~12% allocation overhead
Scenario: 100,000 User (11 properties) records with one nested Client object (11 Properties)
Object structure:
| Metric | Dapper | Merlin | Difference |
|---|---|---|---|
| Mean Execution Time | 726.6 ms | 891.6 ms | +165.0 ms |
| Relative Performance | 1.00x | 1.23x | 23% slower |
| Allocated Memory | 296.21 MB | 323.68 MB | +27.47 MB |
| Allocation Ratio | 1.00x | 1.09x | 9% more |
| Gen0 Collections | 50,000 | 56,000 | +12% |
| Gen1 Collections | 16,000 | 16,000 | No difference |
| Gen2 Collections | 1,000 | 3,000 | +200% |
Merlin introduced approximately:
- 165 ms additional execution time
- 27 MB additional allocations
- ~23% execution overhead
- ~9% allocation overhead
Dapper is faster, but not by much, the trade off is a simpler API
private static readonly QueryEngine DB = new("Local");
[Benchmark(Baseline = true)]
public int Test1_Dapper()
{
using IDbConnection connection = new MySqlConnection(ConStr);
string sql = "SELECT * FROM merlin_bench.users U LEFT JOIN merlin_bench.clients C ON U.user_client = C.client_id;";
var result = connection.Query<UserExtended, Client, UserExtended>( sql, map: (user, profile) => {
user.Client = profile;
return user;
},
param: new { },
splitOn: "client_id" // Tells Dapper where the Profile object fields begin
);
return result.AsList().Count();
}
[Benchmark]
public int Test2_Merlin()
{
var q = new GenericQuery("SELECT * FROM merlin_bench.users U LEFT JOIN merlin_bench.clients C ON U.user_client = C.client_id;");
var data = DB.GetList<UserExtended>(q);
return data.Count;
}
Merlin performs approximately 21% slower than Dapper while allocating only 12% more memory.
The overhead is primarily associated with:
- Metadata-driven mapping
- Property lookup
- Conversion handling
- Automatic object hydration
With a nested object, Merlin maintains nearly the same performance ratio:
| Test | Merlin Overhead |
|---|---|
| Flat Object | 21% slower |
| Nested Object | 23% slower |
This indicates that Merlin's object graph hydration does not introduce significant additional overhead as complexity increases.
The benchmark demonstrates that Merlin trades a small amount of runtime performance for increased mapping flexibility.
Dapper advantages:
- Maximum raw performance
- Minimal abstraction
- Very efficient simple object mapping
Merlin advantages:
- Automatic object hydration
- Nested object support
- Metadata-driven mapping
- Reduced manual mapping code
- Database abstraction without a full ORM tracking model
For business applications where maintainability and development speed are important, a ~20% performance