curiosity
SourceGenerator has been around for a few years now, and while I've always wondered how much of a difference there is between generating code with SourceGenerator and dynamically-generated code with emit, etc., I've always wondered what the difference is.
But I've been too lazy to do it.
indeeddapper aot projectDid a similar thing, but with a particularly aggressive feature and citing experimental properties, so still trying to compare more simply and objectively
This time, taking advantage of the fact that I'm not lazy for a while, I did a test based on SourceGenerator to generate data converted from dbReader to class data.
no generate code when
- Generic Type (can't really handle unknown type T without using emit to generate it dynamically)
- Anonymous Type (SourceGenerator is generated before anonymous class generation, so it hasn't had a chance to be generated yet)
generate code
The details of how to do this will not be written here, interested in the reference/fs7744/SlowestEM
The generated code is dynamically typed with some db results to be closer to the actual usage.
// <auto-generated/>
#pragma warning disable 8019 //disable 'unnecessary using directive' warning
using System;
using ;
using ;
using ;
namespace
{
public static partial class Dog_Accessors
{
public static IEnumerable<> Read(IDataReader reader)
{
var s = new List<Action<, IDataReader>>();
for (int i = 0; i < ; i++)
{
var j = i;
switch ((j).ToLower())
{
case "age":
{
// int?
var needConvert = typeof(int) != (i);
((d,r) => = DBExtensions.ReadToInt32Nullable(r,j,needConvert));
}
break;
case "name":
{
// string
var needConvert = typeof(string) != (i);
((d,r) => = (r,j,needConvert));
}
break;
case "weight":
{
// float?
var needConvert = typeof(float) != (i);
((d,r) => = (r,j,needConvert));
}
break;
default:
break;
}
}
while (())
{
var d = new ();
foreach (var item in s)
{
item?.Invoke(d,reader);
}
yield return d;
}
}
}
}
Test results
mock db, avoiding db layer implementation performance and not handling data type boxing and unboxing correctly
[Benchmark(Baseline = true), BenchmarkCategory("1")]
public void SetClassFirst()
{
Dog dog;
try
{
();
var cmd = ();
= "select ";
using (var reader = ())
{
if (())
{
dog = new Dog();
= (0);
= reader.GetInt32(1);
= (2);
}
}
}
finally
{
();
}
}
[Benchmark, BenchmarkCategory("1")]
public void DapperMappingFirst()
{
var dogs = <Dog>("select ");
}
[Benchmark, BenchmarkCategory("1")]
public void SourceGeneratorMappingFirst()
{
Dog dog;
try
{
();
var cmd = ();
= "select ";
using (var reader = ())
{
dog = <Dog>().FirstOrDefault();
}
}
finally
{
();
}
}
BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4651/22H2/2022Update)
Intel Core i7-10700 CPU 2.90GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
Method | Categories | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
---|---|---|---|---|---|---|---|---|---|---|
SetClassFirst | 1 Entity | 18.38 ns | 0.378 ns | 0.316 ns | 1.00 | 0.00 | 0.0181 | - | 152 B | 1.00 |
SourceGeneratorMappingFirst | 1 Entity | 183.31 ns | 3.525 ns | 3.462 ns | 9.98 | 0.14 | 0.0899 | - | 752 B | 4.95 |
DapperMappingFirst | 1 Entity | 1,336.69 ns | 5.777 ns | 5.121 ns | 72.77 | 1.30 | 0.0343 | - | 288 B | 1.89 |
SetClass | 1000 Entity | 7,700.08 ns | 87.311 ns | 68.167 ns | 1.00 | 0.00 | 6.7749 | 1.1139 | 56712 B | 1.00 |
SourceGeneratorMapping | 1000 Entity | 23,428.85 ns | 262.698 ns | 232.875 ns | 3.04 | 0.03 | 6.8359 | 1.1292 | 57312 B | 1.01 |
DapperMapping | 1000 Entity | 48,880.92 ns | 682.693 ns | 533.002 ns | 6.35 | 0.06 | 13.4888 | 2.1362 | 113048 B | 1.99 |