Category: .NET Core

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….

RecordVisitors–packages and thanks–part 12

Now it is time to see the outdated packages and say thanks to the people that help me created all this project.

For this I install https://github.com/KrystianKolad/DotnetThx . The result is https://github.com/ignatandrei/RecordVisitors/blob/main/src/RecordVisitors/thanks.md

Also, I have installed dotnet outdated tool ( new version!) to see what I should improve

Also , I have installed dotnet-project-licenses to let the user see the license for the project . You can see all the licences at https://github.com/ignatandrei/RecordVisitors/blob/main/src/RecordVisitors/licenses.txt 

Also, a tool that worth is  dotnet-depends – make a try ( allow to run several seconds). You will be impressed by Norton Commander interface…

The CI in github yml it is

– name: helper – see Outdated, thanks , licences and depends

run: |

    cd src/RecordVisitors/       

    dotnet dotnet-outdated

    dotnet dotnet-thx

    dotnet dotnet-project-licenses -i .\RecordVisitors.sln

    # dotnet dotnet-depends

RecordVisitors–Static Analysis with SonarCloud–part 11

Now I should see if the code that I have written is enough good. One way to determine is static analysis – and sonarclould.io is open for open source.

It was pretty simple to setup

1.  I have install the tool  dotnet-sonarscanner

2. Add the secrets from sonnarcloud to Github

3. Add JAVA to the CI Github Action

    – name: Set up JDK 11

uses: actions/setup-java@v1

with:

java-version: 1.11

4. run the build with

cd src/RecordVisitors

dotnet dotnet-sonarscanner begin /k:”ignatandrei_RecordVisitors” /o:”ignatandrei-github” /d:sonar.login=”${{ secrets.SONAR_TOKEN }}” /d:sonar.host.url=”https://sonarcloud.io”

dotnet build

dotnet dotnet-sonarscanner end /d:sonar.login=”${{ secrets.SONAR_TOKEN }}”

And now I have quality gate A –  see https://sonarcloud.io/dashboard?id=ignatandrei_RecordVisitors

RecordVisitors- Readme for Nuget–part 10

Nuget now allow for a package to see a Markdown document. I choose to embed the readme.md file

I just embed into csproj file:

<ItemGroup>
     <None Include=”../../../readme.md” Pack=”true” PackagePath=”\” />
   </ItemGroup>

<PropertyGroup>

<PackageReadmeFile>readme.md</PackageReadmeFile>

</PropertyGroup>

I needed also to make a small modif , to remove HTML comments that were seeing into nuget

$path = “../../README.md”

$path =Resolve-Path $path

Write-Host $path

$fileContent = gc $path

Write-Host $fileContent.Length

For($i=0;$i -lt $fileContent.Length ; $i++){

$line = $fileContent[$i];

If ($line -match ‘^<!–‘) {

$state=’comment’

$fileContent[$i] =$null # because `continue` doesn’t work in a foreach-object

      }

If ($line -match ‘–>$’) {

$state=’content’

$fileContent[$i] =$null

      }

If ($state -eq ‘comment’) {

$fileContent[$i] =$null

      }

}

$fileContent |Set-Content $path

You can see the end result at https://www.nuget.org/packages/recordvisitors#

RecordVisitors–history of urls–part 8

Extending scope :why not record also the history  ? So – again, let the programmer choose what he wants to store https://record-visitors.readthedocs.io/en/latest/RecordVisitors/IRecordVisitorFunctions/methods/GetUrl/  and where to store : https://record-visitors.readthedocs.io/en/latest/RecordVisitors/IUsersRepository/methods/SaveHistory/ .

Created also 2 new endpoints :

/recordVisitors/GetUserId/{userName}

/recordVisitors/UserHistory/{userId}/{dateFrom:datetime:regex(\d{{4}}-\d{{2}}-\d{{2}})}/{dateTo?}

Make a default implementation of having the URL – does not start with api/ , does not contain api .

Make also documentation

Upload the package on Nuget : https://www.nuget.org/packages/RecordVisitors 

It appears simple, but there were 3 hours to implement all

Record visitors- work on issues–part 7

Now it is time to solve some issues  There were 12 issues written by me – you can see here:

https://github.com/ignatandrei/RecordVisitors/issues?q=is%3Aissue+is%3Aclosed

Between the most important:

  1. Improve readme.md with more links and explanations for potential programmer and contributors
  2. Write version in static constructor – to know about the version, if some issue occurs
  3. Added custom link  lastvisitors/minutes/{time:int} to see the latest visitors in a time frame
  4. Add read the docs site https://record-visitors.readthedocs.io/en/latest/
  5. Modification on the interfaces – replacing FUNC property with a real Func

There seems little – but it takes a while.

The links for the project are now

RecordVisitors – xml docs – part 6

Now it is time to write technical documentation  – XML comments to the rescue – first, read the https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/ .

Second, in order to minimize work, make the classes private / internal . See what I REALLY need to use the project – it remains some interfaces and extension methods.

It was helpful

  1. putting warning as errors at csproj level
  2. dotnet watch – to re-test the project each time some changes at the files occur :

dotnet watch test –project AutomatedTestRecord

Also, I have used https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.internalsvisibletoattribute?view=net-5.0 

[assembly: InternalsVisibleTo(“AutomatedTestRecord”)]

in order that the test project works .

Next , I wanted something simple to generate the documents . I know about Sandcastle – https://github.com/EWSoftware/SHFB , however , it was too much for me. I opted for https://github.com/ap0llo/mddocs

You can see the final result here : https://ignatandrei.github.io/RecordVisitors/RecordVisitors/

RecordVisitors- NuGet–part 5

Now it the moment to deploy to NuGet. First , we should have automatic build and deploy to NuGet with GitHub Actions.  This is pretty simple –  the yml becomes

    – name: Pack

run: |

        cd src/RecordVisitors/

        # dotnet pwsh setVersion.ps1

        cd RecordVisitors

        dotnet pack -o nugetPackages

    – name: ‘Upload Artifact’

uses: actions/upload-artifact@v2

with:

name: RecordVisitorsNuget_${{github.run_number}}

path: src/RecordVisitors/RecordVisitors/nugetPackages

retention-days: 1

The modifications of the csproj project are the following

<ItemGroup>
     <PackageReference Include=”Microsoft.SourceLink.GitHub” Version=”1.0.0″ PrivateAssets=”All” />
     <None Include=”../../../readme.md” Pack=”true” PackagePath=”\” />
     <None Include=”../../../docs/rv.png” Pack=”true” PackagePath=”\” />
   </ItemGroup>

   <PropertyGroup>
     <Version>2021.5.29.1730</Version>
     <Authors>Andrei Ignat</Authors>
     <Description>This package help you record your visitors</Description>
     <Title>Record Visitors</Title>
     <PackageTags>C#;.NET;ASP.NET Core</PackageTags>
     <PackageReadmeFile>readme.md</PackageReadmeFile>
     <!–<PackageIconUrl>https://github.com/ignatandrei/RecordVisitors/raw/main/docs/rv.png</PackageIconUrl>–>
     <PackageIcon>rv.png</PackageIcon>
     <RepositoryUrl>https://github.com/ignatandrei/RecordVisitors</RepositoryUrl>
     <PackageProjectUrl>https://github.com/ignatandrei/RecordVisitors</PackageProjectUrl>
     <RepositoryType>GIT</RepositoryType>
     <Copyright>MIT</Copyright>
     <PackageLicenseExpression>MIT</PackageLicenseExpression>
     <IncludeSymbols>true</IncludeSymbols>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>
     <EmbedUntrackedSources>true</EmbedUntrackedSources>
     <Deterministic>true</Deterministic>
     <DebugType>embedded</DebugType>

  </PropertyGroup>
   <PropertyGroup Condition=”‘$(GITHUB_ACTIONS)’ == ‘true'”>
     <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
   </PropertyGroup>

If you want more, read https://alex-klaus.com/better-nuget/  and https://docs.microsoft.com/en-us/nuget/create-packages/package-authoring-best-practices

RecordVisitors–CI –part 4

It will be perfect for any project to have a CI to improve feedback. GitHub actions is helpful here  – just put a gitlab.yml.

After few iterations, mine looks like this

name: BuildAndTest

on:

push:

branches: [ main ]

  pull_request:

    branches: [ main ]

jobs:

  build:

    #runs-on: ubuntu-latest

    runs-on: windows-2019

    steps:

    – uses: actions/checkout@v2

    – name: Setup .NET

      uses: actions/setup-dotnet@v1

      with:

        dotnet-version: 5.0.x

    – name: Restore dependencies

      run: |

        cd src/RecordVisitors

        dotnet restore

    – name: Build

      run: |

        cd src/RecordVisitors

        dotnet build –no-restore

    – name: Test

      run: |

        cd src/RecordVisitors

        dotnet test –no-build –verbosity normal

    – name: code coverage

      run: |

        cd src/RecordVisitors

        dotnet tool restore

        dotnet coverlet AutomatedTestRecord\bin\Debug\net5.0\AutomatedTestRecord.dll –target “dotnet” –targetargs “test RecordVisitors.sln –no-build”  –format opencover –exclude “[SampleWeb*]*”  –exclude “[xunit*]*” –verbosity detailed

        #dotnet coverlet AutomatedTestRecord\bin\Debug\net5.0\AutomatedTestRecord.dll –target “dotnet” –targetargs “test RecordVisitors.sln”  –format opencover  –verbosity detailed

        dotnet reportgenerator “-reports:coverage.opencover.xml” “-targetdir:coveragereport” “-reporttypes:HTMLInline;HTMLSummary;Badges”

    – name: ‘Upload Artifact’

      uses: actions/upload-artifact@v2

      with:

        name: codeCoverage

        path: src/RecordVisitors/coveragereport

        retention-days: 1

    – name: verify code coverage

      run: |

        ls src/RecordVisitors/coverage.opencover.xml

    – uses: codecov/codecov-action@v1

      with:

        files: src/RecordVisitors/coverage.opencover.xml

        fail_ci_if_error: true # optional (default = false)

        #verbose: true # optional (default = false)

    – name: Pack

      run: |

        cd src/RecordVisitors/RecordVisitors

        dotnet pack –include-source –include-symbols

Looks pretty much self-explanatory to me. Build, test, run code coverage and uploads to https://app.codecov.io/gh/ignatandrei/RecordVisitors  ( I am proud : CC 94 % )

RecordVisitors–code coverage–part 3

Now I have to do some code coverage . The easy way is to test the web application – doing an integration test. Use this as a starting point: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-5.0

I have 2 kind of tests: HappyPath  -when all is working ok – and  TestErrors  – when it is not.

The code for all working ok is

public async void TestFakeUser()
{
    // Arrange
    var client = _factory.CreateClient();

    // Act
    var response = await client.GetStringAsync("/recordVisitors/AllVisitors5Min");

    // Assert
    var str = "Jean Irvine";
    Assert.True(response.Contains(str),$"{response} must contain {str}");
                
}

and for testing errors

[Fact]
public  void TestFakeUser()
{
    _factory.RemoveServices = true;
    _factory.RemoveFakeUser = false;
    var ex = Record.Exception(()=>_factory.CreateClient());

    Assert.IsType<ArgumentException>(ex);
            
}
[Fact]
public async void TestNoUser()
{
    _factory.RemoveServices = false;

    _factory.RemoveFakeUser= true;
    // Arrange
    var client = _factory.CreateClient();

    // Act
    var response = await client.GetStringAsync("/recordVisitors/AllVisitors5Min");

    // Assert
    var str = "Jean Irvine";
    Assert.True(response.Contains(str), $"{response} must contain {str}");

}

 

The code coverage has reached 94% after 3 iterations – it is ok. You can see the results at https://app.codecov.io/gh/ignatandrei/RecordVisitors

 

Please make sure that you read also https://andrewlock.net/converting-integration-tests-to-net-core-3/

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.