好奇
SourceGenerator 出現開始,好幾年了,雖然一直好奇用SourceGenerator 生成程式碼 與 emit 等動態生成的程式碼會有多少差距,
但是一直特別懶,不想搞
其實 dapper aot 專案做了類似事情,不過功能特別積極,還引用了實驗特性,所以還是想更為簡單客觀對比
本次乘著自己暫時性不懶了,做了一個基於 SourceGenerator 生成 從 dbReader轉換為 class 資料的測試
no generate code when
- Generic Type (如果不用 emit 動態生成,還真無法處理未知型別 T)
- Anonymous Type (SourceGenerator 生成時機要早於匿名類生成,所以還沒機會生成)
generate code
具體怎麼做的就這裡不寫了,感興趣參考 https://github.com/fs7744/SlowestEM
生成的程式碼帶有一定 db結果動態型別處理,以此更接近實際使用
// <auto-generated/>
#pragma warning disable 8019 //disable 'unnecessary using directive' warning
using System;
using System.Data;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
namespace SlowestEM.Generator
{
public static partial class Dog_Accessors
{
public static IEnumerable<BenchmarkTest.Dog> Read(IDataReader reader)
{
var s = new List<Action<BenchmarkTest.Dog, IDataReader>>(reader.FieldCount);
for (int i = 0; i < reader.FieldCount; i++)
{
var j = i;
switch (reader.GetName(j).ToLower())
{
case "age":
{
// int?
var needConvert = typeof(int) != reader.GetFieldType(i);
s.Add((d,r) => d.Age = DBExtensions.ReadToInt32Nullable(r,j,needConvert));
}
break;
case "name":
{
// string
var needConvert = typeof(string) != reader.GetFieldType(i);
s.Add((d,r) => d.Name = DBExtensions.ReadToString(r,j,needConvert));
}
break;
case "weight":
{
// float?
var needConvert = typeof(float) != reader.GetFieldType(i);
s.Add((d,r) => d.Weight = DBExtensions.ReadToFloatNullable(r,j,needConvert));
}
break;
default:
break;
}
}
while (reader.Read())
{
var d = new BenchmarkTest.Dog();
foreach (var item in s)
{
item?.Invoke(d,reader);
}
yield return d;
}
}
}
}
測試結果
mock db, 避免 db層實現效能和沒有正確處理資料型別裝箱拆箱問題
[Benchmark(Baseline = true), BenchmarkCategory("1")]
public void SetClassFirst()
{
Dog dog;
try
{
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = "select ";
using (var reader = cmd.ExecuteReader(CommandBehavior.Default))
{
if (reader.Read())
{
dog = new Dog();
dog.Name = reader.GetString(0);
dog.Age = reader.GetInt32(1);
dog.Weight = reader.GetFloat(2);
}
}
}
finally
{
connection.Close();
}
}
[Benchmark, BenchmarkCategory("1")]
public void DapperMappingFirst()
{
var dogs = connection.QueryFirst<Dog>("select ");
}
[Benchmark, BenchmarkCategory("1")]
public void SourceGeneratorMappingFirst()
{
Dog dog;
try
{
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = "select ";
using (var reader = cmd.ExecuteReader(CommandBehavior.Default))
{
dog = reader.ReadTo<Dog>().FirstOrDefault();
}
}
finally
{
connection.Close();
}
}
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 實體 | 18.38 ns | 0.378 ns | 0.316 ns | 1.00 | 0.00 | 0.0181 | - | 152 B | 1.00 |
SourceGeneratorMappingFirst | 1 實體 | 183.31 ns | 3.525 ns | 3.462 ns | 9.98 | 0.14 | 0.0899 | - | 752 B | 4.95 |
DapperMappingFirst | 1 實體 | 1,336.69 ns | 5.777 ns | 5.121 ns | 72.77 | 1.30 | 0.0343 | - | 288 B | 1.89 |
SetClass | 1000 實體 | 7,700.08 ns | 87.311 ns | 68.167 ns | 1.00 | 0.00 | 6.7749 | 1.1139 | 56712 B | 1.00 |
SourceGeneratorMapping | 1000 實體 | 23,428.85 ns | 262.698 ns | 232.875 ns | 3.04 | 0.03 | 6.8359 | 1.1292 | 57312 B | 1.01 |
DapperMapping | 1000 實體 | 48,880.92 ns | 682.693 ns | 533.002 ns | 6.35 | 0.06 | 13.4888 | 2.1362 | 113048 B | 1.99 |