Microsoft partner software engineer Stephen Toub has posted at length about performance improvements in the forthcoming version of .NET, declaring that setting Dynamic Profile-Guided Optimization (PGO) on by default “might be the most valuable PR [pull request] in all of .NET 8.”
.NET uses just-in-time (JIT) compilation, where intermediate language is compiled to native code on first use. There is a trade-off between speed of compilation and optimization, and since fast start-up is a high priority, the JIT does a first try with little optimization, and then after the application starts it recompiles the most performance-critical code it can detect in a process called tiering. The current version of .NET, 7.0, improved this by introducing the ability to replace running compiled code on the fly even in a long-running method, called on-stack replacement.
PGO is where an application is run with instrumentation, then optimized according to data from the instrumentation. Dynamic PGO is a variant where this is done by the JIT, instrumenting the initial tier and using the data for the second-tier optimization. This was previewed in .NET 6, improved in .NET 7, but remained off by default. In .NET 8 it is on by default, and Toub declared that the “one character PR to enable it might be the most valuable PR in all of .NET 8.”
How much difference does Dynamic PGO make? It depends of course; but Toub quotes with/without figures throughout his 50,000 word post that make a convincing case – such as one IList example which takes around 40 percent less time. In the same example though Toub makes the point that PGO is not a substitute for careful coding: replacing IList (an interface) with List further speeds up the time and in this case PGO saves only 14 percent. Therefore in cases where an interface can be replaced with a derived type that coding change might make more difference than PGO.
Native AOT (ahead of time) compilation was introduced in .NET 7 and eliminates the need for a JIT. In .NET 8 this becomes more useful as it has been applied to ASP.NET applications, where before these were not supported. There has been a focus on size as well as performance with dramatic results in some cases: Toub quotes an example where a 13MB executable becomes 1.5MB.
Other topics in Toub’s post include vectorization, threading, bounds checking, Blazor, the Mono runtime used for mobile and browser applications, concurrency, exceptions, string handling and more.
Low-level performance improvements may be less obvious than things like new language or framework features, but make an immediate difference to developers since they apply to existing as well as new code.
A comment to the post raises a key point though: why is Visual Studio itself “still stuck on the old .NET framework which is so much slower?” There is no comment yet on this from Microsoft, though since WPF (Windows Presentation Foundation) has been ported to .NET Core it seems plausible.
Like Visual Studio, many business applications were written in .NET Framework and have never been ported; this new post shows why doing so is worth the effort. “We just migrated a large .NET Framework app to .NET 6 and the perf improvements were large,” noted a developer on Hacker News.
.NET 8 is a long-term support release and general availability is expected in mid-November.