Currently the runtime takes a lock surrounding external access to a class that contains any static constructor (whether written explicitly or implicitly). Those locks add up to substantial runtime cost, especially on frequently accessed classes.
Since we have progressive JIT in the CLR now, there is an opportunity to swap out the code that executes with more efficient code that removes the locking once the static constructor is known to have been initialized. (For example, the first pass JIT code containing the static lock could contain a final instruction to trigger the re-JIT to remove the init locks. So that way, when the static constructor runs, it could trigger the code generation + swap logic.)
I would prefer to have that implemented in such a way that doesn't affect debugging -- we'll only hit the static constructor once in any case, so I suspect this would be safe to do even on CLI modules compiled without the optimization flag.
I did a quick check of existing issues and I don't see anything obviously related... Please let me know if this has already been addressed or discussed.
As an addendum to this proposal, there could be an optimization pass after a module constructor has run that would swap out the JITed code for all static initializers that have already been run as a result of the module constructor. I don't know empirically if having that as an explicit step would produce any net benefit over solving the problem in a universal, generic way. But there might be a runtime cost benefit to allow the JIT to generate less optimal code during module init as a cost savings, knowing optimized code can be JITed after module init is completed.
This optimization is performed by tiered JIT in .the current NET Core 3.0 master today.
@jkotas that's excellent to know!
Will this be coming to .NET 4.8?
The changes in .NET Framework 4.8+ are limited to reliability and security fixes. There are no new major features (e.g. new APIs) or performance improvements (like this one) planned for .NET Framework 4.8.
More on this is in https://devblogs.microsoft.com/dotnet/net-core-is-the-future-of-net/
@jkotas I'm aware of and supportive of the .NET 5.0 goals. Bummer that it won't be in .NET 4.8 though... since that's likely the last release of legacy .NET that we'll see.
As my org's .NET architect, I realistically see many .NET apps living on for several years to come before everything gets phased out or ported to .NET 5+, and it sure would be nice to be able to simply run the latest full framework installer and get those performance benefits. I'm sure others are in a similar position.
On the flip side, that will be additional motivation to port to .NET 3.1 once it comes out (as we need the LTS support)...
@jkotas I'm just coming up to speed on the .NET 4.8 -- the announcement says the following:
New Features in .NET Framework 4.8
Runtime – JIT improvements
The JIT in .NET 4.8 is based on .NET Core 2.1. All bug fixes and many code generation-based performance optimizations from .NET Core 2.1 are now available in the .NET Framework.
I've posted https://github.com/microsoft/dotnet/issues/1038 asking for more details.
The JIT in .NET 4.8 is based on .NET Core 2.1.
This change is in .NET Core 3.0 which isn't the Jit in 4.8, its the .NET Core 2.1 LTS Jit
This change is in .NET Core 3.0 which isn't the Jit in 4.8, its the .NET Core 2.1 LTS Jit
Also, I assume that in this case, any change in the JIT relies on Tiered-Compilation being available in the runtime. Tiered-Compilation isn't currently available in .NET Framework 4.X (and may never be).
Most helpful comment
This optimization is performed by tiered JIT in .the current NET Core 3.0 master today.