Tests, Tests, Tests

Die letzte Woche vor Weihnachten steht an und ich habe eine kleine Weihnachtsmission.

Der DatenMeister bekommt die Aufgabe ein Resource-Management für Personen und Projekte darzustellen. Aktuell passiert bei mir viel in Excel, was unsäglich und untauglich ist.

Was ist der DatenMeister? Eigentlich nur ein kleiner Webdienst, der mit Daten umgehen kann? Nicht sonderlich professionell, nicht umfangreich, aber doch klein und fein und flexibel… Aktuell nutze ich für das IssueTracking für den DatenMeister… Der IssueMeister.

Aber was wollte ich eigentlich schreiben:

Es gibt weiterhin ein kontinuierlicher Anstieg von Tests, der prüft, ob die Grundfunktionalitäten im Backend des DatenMeister noch gehen. Hierzu NUnit.

Für das Front-End gibt es auch was… Wobei ich hier nur halb glücklich bin, da ich es noch nicht in die Jenkins-Pipeline integriert hatte und es direkt auf der lokalen Datenbank arbeitet. Es basiert auf Chai.

Client-Side Test Cases

Bye Bye Twitter

Schweren Herzens trenne ich von meinem Troll-Account auf Twitter und gehe zu Mastadon über. Der neue Link lautet: TheUndeadable (@martinbrenn@social.anoxinon.de) – social.anoxinon.de – Mastodon

Die Themen werden durchmischter sein, da im Prinzip das große Ziel zur Beendigung der Corona-Maßnahmen in Aussicht ist und wir aktuell noch eine infektionsreiche Zeit als Nachklang der langen Isolationszeiten haben. (⬅️These)

In diesem Sinne… So richtig weiß ich noch nicht, wie ich die sozialen Portale und den Blog nutzen werde. Ich hoffe, dass ich insgesamt mehr Zeit dafür finde.

Benchmark StringIsNullOrEmtpy

In todays‘ Twitter post (unfortunately, I lost the link and I don’t find anything in Twitter), I saw the recommendation to use the following pattern.

Don’t use

if (String.IsNullOrEmpty(text))

Use:

if(text is {Length: 0})

Neglecting the style itself, I was heavily interested to understand the runtime impact of this style.

BenchmarkDotNet is quite comfortable here…. But not talking to much about that… Here is the Benchmark Code, just for the pattern:

Static Scenario

[Benchmark()]
public int StringIsLength()
{
    var result = 0;
    for (var n = 0; n < 10000; n++)
    {
        var x = "";
        if (x is { Length: 0 }) { result++; }

        x = "a";
        if (x is { Length: 0 }) { result++; }
    }

    return result;
}

Iteration by 10.000 was needed due to the following result. The IL language looks like the following:

// [49 13 - 49 41] // String.IsNullOrEmpty
IL_001b: call bool [System.Runtime]System.String::IsNullOrEmpty(string)
IL_0020: brfalse.s IL_0026

versus

// [17 13 - 17 36] // x is {Length:0}
IL_000c: ldloc.2 // x
IL_000d: brfalse.s IL_001b
IL_000f: ldloc.2 // x
IL_0010: callvirt instance int32 [System.Runtime]System.String::get_Length()
IL_0015: brtrue.s IL_001b

It heavily now depends on the implementation of get_Length and String.IsNullOrEmpty…

// .Net Code
    public static bool IsNullOrEmpty(string? value)
    {
        return (value == null || 0 == value.Length);
    }

So… looks like x is {Length: 0} can win because it avoids an indirect jump…

The results:

MethodMeanErrorStdDevCode Size
StringIsLength5.800 us0.0650 us0.0508 us65 B
StringIsNullOrEmpty3.131 us0.0077 us0.0068 us17 B
Benchmark Table

StringIsNullOrEmpty is faster and smaller… What happened?

Looking at the disassembly of x is {Length:0}

## .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
```assembly
; Program.StringIsLength()
;         var result = 0;
;         ^^^^^^^^^^^^^^^
       xor       eax,eax
;         for (var n = 0; n < 10000; n++)
;              ^^^^^^^^^
       xor       edx,edx
       mov       rcx,1F848B23020
       mov       rcx,[rcx]
       mov       r8,1F848B2A110
       mov       r8,[r8]
;             var x = "";
;             ^^^^^^^^^^^
M00_L00:
       mov       r9,rcx
;             if (x is { Length: 0 }) { result++; }
;             ^^^^^^^^^^^^^^^^^^^^^^^
       cmp       dword ptr [r9+8],0
       jne       short M00_L01
       inc       eax
;             x = "a";
;             ^^^^^^^^
M00_L01:
       mov       r9,r8
;             if (x is { Length: 0 }) { result++; }
;             ^^^^^^^^^^^^^^^^^^^^^^^
       cmp       dword ptr [r9+8],0
       jne       short M00_L02
       inc       eax
M00_L02:
       inc       edx
       cmp       edx,2710
       jl        short M00_L00
       ret
; Total bytes of code 65

As expected… Now String.IsNullOrEmpty

## .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
```assembly
; Program.StringIsNullOrEmpty()
;         var result = 0;
;         ^^^^^^^^^^^^^^^
       xor       eax,eax
;         for (var n = 0; n < 10000; n++)
;              ^^^^^^^^^
       xor       edx,edx
;                 result++;
;                 ^^^^^^^^^
M00_L00:
       inc       eax
       inc       edx
       cmp       edx,2710
       jl        short M00_L00
       ret
; Total bytes of code 17

??! It just counts from 0 to 10.000 and returns the increase… The jitter just strips of the String.IsNullOrEmpty call and returns the result… It knows, that String.IsNullOrEmpty(„“) is false and String.IsNullOrEmpty(„a“) is true. (Ok, the jitter could also optimize the loop since the result is static)

Conclusion

String.IsNullOrEmpty is faster in static scenarios, now changing to dynamic scenarios. Here, something else will be figured out

Dynamic Scenario

MethodMeanErrorStdDevCode Size
StringIsLengthDynamic6.949 us0.0178 us0.0157 us104 B
StringIsNullOrEmptyDynamic6.942 us0.0229 us0.0215 us104 B
Dynamic Scenario

Same performance!! ? (Code size change is due to some strange stuff caused by BenchmarkDotNet)?

empty = "";
nonEmpty = "a";
[Benchmark()]
public int StringIsLengthDynamic()
{
    var result = 0;
    for (var n = 0; n < 10000; n++)
    {
        if (empty is { Length: 0 }) { result++; }
        if (nonEmpty is { Length: 0 }) { result++; }
    }

    return result;
}

Bäng…

;             if (nonEmpty is { Length: 0 }) { result++; }
;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
M00_L01:
       mov       rdx,238B4367348
       mov       rdx,[rdx]
       test      rdx,rdx
       je        short M00_L02
       cmp       dword ptr [rdx+8],0
       jne       short M00_L02

vs

;             if (string.IsNullOrEmpty(empty)) { result++; }
;             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
M00_L00:
       mov       rdx,rax
       test      rdx,rdx
       je        short M00_L01
       cmp       dword ptr [rdx+8],0
       jne       short M00_L02,

Conclusio

It does not matter which style to use for performance reasons… It just depends upon which style you prefer. The jitter is aware to optimize String.IsNullOrEmpty.

Here is everything: stringisnullorempty.asm (github.com)

SI-Modell

Ein kleiner Schmankerl über die Theorie der Epidemiologie (oder wie man das schöne Wort auch immer schreibt):

SI-Modell – Wikipedia

Eine mathematische Modellierung der Infektionszahlen unter der Annahme, dass das Virus nicht ausrottbar ist… Sondern Maßnahmen nur die Zeit verlängern, bis die gesamte Bevölkerung dem Virus ausgesetzt worden ist.

Diese Modelle finde ich persönlich in der Hinsicht interessant, dass ein paar Aussagen, die ‚trivial‘ erscheinen, doch mathematisch exakt modelliert werden können.

Natürlich gilt auch wie immer die Aussage: Ein Modellierung ist nur so gut, wie die Parameter bekannt sind und wie das Modell die Realität beschreibt. Und ich glaube nicht, dass ich mich aus dem Fenster lehne, dass hier noch einige Lücken zu schließen sind.

Aber nichtsdestotrotz möchte ich diesen Beitrag mit einem Plädoyer zur Impfung abschließen:

Ich bin in vieler Hinsicht kein Freund ganz vieler Maßnahme und halte die Politisierung der Impfung für einen Fehler. Dennoch finde ich, dass man über seinen eigenen politischen Schatten springen sollte und das medizinisch Notwendige und Sinnvolle zur Bekämpfung der Pandemie tun soll.

Windows Link mit Android

Bei der Einrichtung meines neuen Telefons habe ich eine praktische Funktion entdeckt:

Man kann direkt Anwendungen (neudeutsch: Apps) direkt auf dem PC starten und anzeigen… es werden dann ‚richtige‘ Fenster auf dem Desktop angezeigt.

Dies funktioniert mit der ‚Ihr Smartphone‘ unter Windows 10.

Sehr amüsant… jetzt brauch ich nur noch eine App, die nur auf dem Handy funktioniert und nicht auf dem PC…

.Net Core – Doppelt geladene Assemblies

Wurde mal wieder mit folgender Fehlermeldung begrüßt:

System.InvalidCastException :
[A]DatenMeister.Modules.ZipCodeExample.ZipCodeModel cannot be cast to
[B]DatenMeister.Modules.ZipCodeExample.ZipCodeModel.
Type A originates from
'DatenMeister.Modules.ZipCodeExample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Projekte\DatenMeister\src\Tests\DatenMeister.Tests\bin\Debug\net5.0\DatenMeister.Modules.ZipCodeExample.dll'.
Type B originates from
'DatenMeister.Modules.ZipCodeExample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Projekte\DatenMeister\src\Tests\DatenMeister.Tests\bin\Debug\net5.0\DatenMeister.Modules.ZipCodeExample.dll'.

Was ist passiert?
Über ein dynamische Assembly.LoadFrom habe ich über ein Plugin-System die Assembly ZipCodeExample eingeladen. Weiterhin habe ich im Quelltext über eine ProjectReference parallel die Typen in der Assembly genutzt.

.Net Core hat hier ein geändertes Verhalten im Gegensatz zum .Net Framework und erzeugt zwei Instanzen der Assembly. Das heißt, dass die Instanziierungen der Typen in dieser Assembly, je nach Aufrufer unterschiedlich sind, obwohl sie scheinbar vom gleichen Typ sind… Den Unterschied erkennt man über die Eigenschaft m_assembly in dem dahinterliegenden Typ.

Nochmal:
Programm lädt Plugins ein und Plugin-Start-Methode auf
Plugins instanziiert ein Hilfsobject ZipCodeModel und speichert es in einer ObjektDatenbank

Programm ruft eigentliche Routine auf, die das ZipCodeModel per ProjectReference nutzt
Die Objekt-Datenbank gab das per Plugin erzeugte Objekt zurück (da gleicher FullName).
Allerdings passen die Typen nicht zueinander, da das per Plugin erzeugte Objekt vom anderen Typ als die Project Reference ist.

Unschön… hier hatte ich auch mal vor einiger Zeit ein Issue erzeugt über das ich eine sehr schöne Erklärung erhalten hatte: Assembly Loading via ProjectReference and Assembly.LoadFile upon same file leads to two instances · Issue #36787 · dotnet/runtime (github.com)

Die Lösung:
Über folgenden Artikel habe ich die Information erhalten, dass Load statt LoadFrom das doppelte Einladen vermeidet. ==> Plugin Loader is now working correctly by using Load instead of Load … · mbrenn/datenmeister-new@3c87d92 (github.com)

Das Ergebnis:

Success, wo man auch immer hinschaut.

0 Tage Scholz,

Nach 16 Jahren Merkel, habe ich nun vier Bundeskanzler:innen erlebt:

  • Schmidt (Ok, im Alter bis 2 Jahre)
  • Kohl (mit der Erinnerung an den Mauerfall in der Kindheit)
  • Schröder (Elefantenrunde)
  • Merkel (naja… mal schauen, was die Erinnerung in einigen Jahren bringt)

Nun führt Bundeskanzler Scholz unser Land und ich persönlich dem Herren und seinen Minister:innen auch zu meinem Vorteil viel Erfolg! Warten wir auf die ersten 100 Tage ab, zu der ich jeder neuen Rollen bedingungslos erstmal nur das Positive sehe. Und aktuell sind die Herausforderungen groß… sehr groß!

In diesem Sinne… Peace out!

Apropos… Thema das Tages: Omikron… ich bleibe gespannt, nach diversen Berechnungen werden wir hier in Deutschland um Januar bis Februar den Omikron-Höhepunkt erreicht haben und die Welle wird dazu führen, dass jeder von uns dem Virus ausgesetzt werden wird. Daher: Bitte lasst euch bedingungslos impfen. Falls jemand sich nicht impfen möchte, würde mich die Motivation dieser Person (vorurteilsfrei) interessieren, schreibt mir… brenn@depon.net (as you know). Falls jemand in die Erstimpfung (erwiesenermaßen) geht, sponsore ich auch eine Tafel Ritter Sport nach Wahl! *

*) Natürlich nur an nette Menschen, die ich persönlich auch kennen könnte.

Visual Studio 2022

Visual Studio 2022

Installiert und läuft… leider ist das Resharper-Plugin von JetBrains noch nicht kompatibel zu VS2022… Die Preview-Version habe ich nicht ausprobiert.

Nichtsdestotrotz sind mir bisher keine Mängel aufgefallen.