Benchmarking RSCG vs Reflection

I make a microservices Buffet . In this I consider having email as a service . When the DevOps wants email, he can choose between various plugins ( simple smtp email, gmail, exchange, others). Those plugins can have various properties – that must be edited by the primary administrator of the microservice. The properties can be discovered at runtime ( via Reflection ) o r at build time ( via Roslyn Source Code Generators  – RSCG ).

But – we should see what is faster, right ?   And the feeling is that RSCG is always faster – but it is , really ? Let’s see…

So = let’s make a test with https://github.com/dotnet/BenchmarkDotNet . You can have the source code by going to https://github.com/ignatandrei/AOP_With_Roslyn/tree/master/AOPMethods .

First , the class that is tested

public partial class EmailSmtpClientMS 
{

    public EmailSmtpClientMS()
    {

        Port = 25;

    }
    public string Name { get; set; }


    public string Type
    {
        get
        {
            return this.GetType().Name;
        }
    }
    public string Host { get; set; }
    public int Port { get; set; }

    public string Description
    {
        get
        {
            return $"{Type} {Host}:{Port}";
        }
    }
}

 

Second, with AOPMethods I generate the read properties values – properties that  you can read – via a dictionary and via a switch. This can be achieved simply :

[AutoMethods(template = TemplateMethod.CustomTemplateFile, CustomTemplateFileName = "ClassToDictionary.txt")]
    public partial class EmailSmtpClientMS 

And this will be generated by RSCG for the switch

protected object GetValueProperty(string propName)
{
    switch (propName)
    {
        //true true
        case "Name":
            return this.Name;


        //true false
        case "Type":
            return this.Type;


        //true true
        case "Host":
            return this.Host;


        //true true
        case "Port":
            return this.Port;


        //true false
        case "Description":
            return this.Description;



        default:
            throw new ArgumentException("cannot find property " + propName);
    }
}

and for the dictionary

private IDictionary<string, PropertyHelper> MyProperties()
{
    var data = new Dictionary<string, PropertyHelper>();
    PropertyHelper ph;
    ph = new PropertyHelper();
    ph.Name = "Name";
    ph.Type = "string";
    ph.CanRead = !false;
    ph.CanWrite = !false;

    ph.Value = this.Name;

    data.Add("Name", ph);
    //Name string     
    ph = new PropertyHelper();
    ph.Name = "Type";
    ph.Type = "string";
    ph.CanRead = !false;
    ph.CanWrite = !true;

    ph.Value = this.Type;

    data.Add("Type", ph);
    //Type string     
    ph = new PropertyHelper();
    ph.Name = "Host";
    ph.Type = "string";
    ph.CanRead = !false;
    ph.CanWrite = !false;

    ph.Value = this.Host;

    data.Add("Host", ph);
    //Host string     
    ph = new PropertyHelper();
    ph.Name = "Port";
    ph.Type = "int";
    ph.CanRead = !false;
    ph.CanWrite = !false;

    ph.Value = this.Port;

    data.Add("Port", ph);
    //Port int     
    ph = new PropertyHelper();
    ph.Name = "Description";
    ph.Type = "string";
    ph.CanRead = !false;
    ph.CanWrite = !true;

    ph.Value = this.Description;

    data.Add("Description", ph);
    //Description string     

    return data;

}

 

The spec for benchmark are :


BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1052 (21H1/May2021Update)
Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
.NET SDK=5.0.301
  [Host]     : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT
  DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT

 

Third, I benchmark obtaining one single property – the Host – via the 3 methods:

public partial class EmailSmtpClientMSOneProperty: EmailSmtpClientMS
{
    [Benchmark]
    public string GetHostReflection()
    {
        return this.GetType().GetProperty("Host").GetValue(this).ToString();
    }
    [Benchmark]
    public string GetHostViaDictionary()
    {
        return this.ReadMyProperties()["Host"].ToString();
    }
    [Benchmark]
    public string GetHostViaSwitch()
    {
        return this.GetValueProperty("Host").ToString();
    }
}

And in Program.cs

BenchmarkRunner.Run<EmailSmtpClientMSOneProperty>(
    ManualConfig
        .Create(DefaultConfig.Instance)
        .WithOption(ConfigOptions.DisableOptimizationsValidator, true)
        );

( of course, I have added on the class
//[SimpleJob(RuntimeMoniker.Net50)]
//[ShortRunJob(RuntimeMoniker.Net50)]
//[DryJob(RuntimeMoniker.Net50)]
[Orderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared)]
//[SimpleJob(RuntimeMoniker.NetCoreApp31)]
[RPlotExporter]
[CsvMeasurementsExporter]
[MemoryDiagnoser]
[HtmlExporter]
[MarkdownExporterAttribute.GitHub]
)
The results are in ns –so, the less/smaller , that means  better results.

The results are here in HTML form :

 

 

Method Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
GetHostViaSwitch 18.07 ns 0.434 ns 0.549 ns 18.02 ns
GetHostReflection 144.13 ns 2.582 ns 5.501 ns 142.13 ns
GetHostViaDictionary 451.59 ns 12.363 ns 33.635 ns 441.72 ns 0.3057 640 B

The graphic may be more interesting:

 

Surprised ? The RSCG Switch Property is indeed the fastest one – but the Reflection is faster than RSCG Property Dictionary ( or , at least , for my implementation).

However, I realized that in real life , I will retrieve all properties in a Dictionary to be edited . So all implementations should occur the penalty of creating a Dictionary . Time for next benchmark . This time , the code is

[Benchmark]
public IDictionary<string,object> GetHostReflection()
{
    var props = this.GetType()
        .GetProperties()
        .Where(it=> it.CanWrite)
        .ToDictionary(it => it.Name, it=>it.GetValue(this));
        ;
    return props;
                
}
[Benchmark]
public IDictionary<string, object> GetHostViaDictionary()
{
    var props = this.ReadMyProperties();
    return props;
            
}
[Benchmark]
public IDictionary<string, object> GetHostViaSwitch()
{
    var props = ReadProperties
        .ToDictionary(it => it, it => GetValueProperty(it));
    return props;
            
            
}

 

and the results are:

Method Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
GetHostViaDictionary 462.1 ns 14.70 ns 40.97 ns 453.6 ns 0.3052 640 B
GetHostViaSwitch 479.5 ns 7.34 ns 7.54 ns 479.9 ns 0.2708 568 B
GetHostReflection 973.0 ns 78.35 ns 231.01 ns 911.5 ns 0.1984 416 B

 

Now the graphic will help:

 

Interesting , right ?

Reflection = as normal –  is the slowest one. But the difference between RSCG switch and RSCG Dictionary is not too much…

Conclusion 1:  the feeling  was right at the end. But – the first result was deceiving

Conclusion 2: Creating a dictionary is more time consuming than a simple reflection for one property retrieved

Conclusion 3: I do prefer RSCG Dictionary vs RSCG switch – less work for me as a programmer and similar time results.

Conclusion 4: do not over engineer if you do not feel the need . For just one property, Reflection is better….

Conclusion 5: This is not final. I should also write the values of the properties . Maybe next time a new benchmark….