Location>code7788 >text

A performance test experiment based on SourceGenerator generation of data converted from dbReader to class data

Popularity:942 ℃/2024-07-30 19:45:42

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