一个基于 SourceGenerator 生成 从 dbReader转换为 class 数据的性能测试实验

好奇

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
posted @ 2024-07-30 17:28  victor.x.qu  阅读(359)  评论(0编辑  收藏  举报