一个基于 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


MethodCategoriesMeanErrorStdDevRatioRatioSDGen0Gen1AllocatedAlloc Ratio
SetClassFirst1 实体18.38 ns0.378 ns0.316 ns1.000.000.0181-152 B1.00
SourceGeneratorMappingFirst1 实体183.31 ns3.525 ns3.462 ns9.980.140.0899-752 B4.95
DapperMappingFirst1 实体1,336.69 ns5.777 ns5.121 ns72.771.300.0343-288 B1.89
SetClass1000 实体7,700.08 ns87.311 ns68.167 ns1.000.006.77491.113956712 B1.00
SourceGeneratorMapping1000 实体23,428.85 ns262.698 ns232.875 ns3.040.036.83591.129257312 B1.01
DapperMapping1000 实体48,880.92 ns682.693 ns533.002 ns6.350.0613.48882.1362113048 B1.99
原创作者: fs7744 转载于: https://www.cnblogs.com/fs7744/p/18332993
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值