Zoals haast elk jaar, heeft Stephen Toub ook dit jaar zich weer gebogen over de performanceverbeteringen in de laatste .NET versie (dit jaar dus .NET 10). Wil je meer details over de manier waarop hij deze benchmark uitvoert? Kijk dan eens op zijn blogpost.
Voor developers die lijken op mij en graag even een samenvatting van zijn uitgebreide artikel hebben, heb ik hier de belangrijkste verbeteringen op een rij gezet.
Just in Time Compiler
Binnen alle onderdelen van .NET springt de Just-In-Time (JIT) compiler eruit als een van de meest invloedrijke. Elke .NET-applicatie, of het nu een klein consoleprogramma is of een grootschalige enterprise-service, is uiteindelijk afhankelijk van de JIT om intermediate language (IL) code om te zetten in geoptimaliseerde machinecode.
Zoals bij veel programmeertalen kent .NET historisch gezien een “performance penalty” die optreden bij het gebruik van hoog-niveau taalconstructies zoals interfaces, iterators en delegates. Elk jaar wordt de JIT beter in het weg optimaliseren van lagen van abstractie, zodat ontwikkelaars eenvoudige code kunnen schrijven en toch uitstekende prestaties behalen. DotNet 10 zet deze traditie voort. Het resultaat is dat idiomatisch C# (met interfaces, foreach-loops, lambda’s, enz.) nog dichter in de buurt komt van de ruwe snelheid van zorgvuldig geschreven en handmatig geoptimaliseerde code.
Object stack allocation
Een van de meest opwindende gebieden van vooruitgang binnen .NET 10 is het uitgebreide gebruik van escape-analyse om stack-allocatie van objecten mogelijk te maken. Escape-analyse is een compilertechniek die bepaalt of een object dat binnen een methode wordt gealloceerd, die methode “ontsnapt” — met andere woorden, of dat object nog bereikbaar is nadat de methode is beëindigd.

In het bovenstaande voorbeeld zie je belangrijke verschillen tussen .NET 9 en .NET 10:
Doordat er in dit voorbeeld een Func<> en daarna de DoubleResult code wordt aangeroepen met in beide gevallen de parameter y, worden er in .NET 9 meerdere allocaties gemaakt. In .NET 10 is dit verder geoptimaliseerd waardoor er aanzienlijk minder geheugenallocatie plaatsvindt, en de performance ook verbeterd. Omdat dit principe in heel veel vormen in code voorkomt is de overall performance en geheugen verbetering van .NET 10 substantieel ten opzichte van .NET 9.
De-virtualisatie
Interfaces en virtual methods zijn een belangrijk aspect van .NET en de snelheid waarmee deze abstracties door de JIT-compiler omgezet worden in machinecode drukt zijn stempel op de performance van een applicatie.
In de bovenstaande code zie je het verschil tussen .NET 9 en .NET 10 duidelijk terug:
Threading
In .NET is er bij asynchrone methodes (vaak de defacto standaard tegenwoordig) altijd een risico dat er een deadlock plaatsvindt op de verschillende threads. Hoewel in de praktijk de risico’s op deadlocks (vooral in UI applicaties) nog steeds bestaan, is er in .NET 10 een hoop werk gedaan om dit risico te verlagen.
LINQ
Het gebruik van LINQ in moderne applicaties is enorm. Het is je niet voor te stellen dat je een applicatie schrijft waar geen LINQ gebruikt wordt. Wanneer het gaat over database query’s dan zal de grootste performance hit komen uit de snelheid van de query naar de database. Tegelijkertijd zit er stiekem vaak ook veel LINQ to objects in een applicatie. Die code zal er veel baat bij hebben dat er behoorlijke performance verbeteringen in de verwerking van de code zitten. De onderstaande lijst laat een serie acties zijn waarbij er een combinatie is tussen een actie in LINQ (bijvoorbeeld een orderby of een distinct) en daarna een contains:
Het mag duidelijk zijn dat de verwerking in .NET 10 aanzienlijk sneller is dan in .NET 9.
Filesystem I/O operations
Er zijn de laatste jaren al substantiële verbeteringen doorgevoerd op het gebied van I/O taken, zoals het herschrijven van de FileStream in .NET 6. In .NET 10 zitten nog een paar mooie verbeteringen die vooral ook een aantal optimalisaties voor Linux systemen bevatten. Als voorbeeld, had de oude FileSystemWatcher de neiging om behoorlijk wat geheugen in te nemen omdat er gaandeweg objecten konden “lekken” in het geheugen:
JSON
Je staat er misschien niet zo bij stil, maar als je een web API bouwt vind er onder de motorkap behoorlijk wat serialisatie plaats. Voor het serializeren van objecten is er in .NET 10 een mooie optimalisatie doorgevoerd:
Ook voor het uitvoeren van bulk acties zijn er belangrijke stappen gemaakt.
In het bovenstaande voorbeeld wordt er een RemoveAll met een predicate aangeroepen in plaats van handmatig door een lijst te itereren om elementen te verwijderen.
En nog veel meer …
Zoals al gezegd, de benchmark van Stephen is zeer uitgebreid en terug te lezen op zijn blogpost. Ik kan je zeker aanraden om hier eens te kijken, maar niet schrikken van de lengte van het artikel.



