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.
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.
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…
So… looks like x is {Length: 0} can win because it avoids an indirect jump…
The results:
Method
Mean
Error
StdDev
Code Size
StringIsLength
5.800 us
0.0650 us
0.0508 us
65 B
StringIsNullOrEmpty
3.131 us
0.0077 us
0.0068 us
17 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
Method
Mean
Error
StdDev
Code Size
StringIsLengthDynamic
6.949 us
0.0178 us
0.0157 us
104 B
StringIsNullOrEmptyDynamic
6.942 us
0.0229 us
0.0215 us
104 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.
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.
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.
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.