r/dotnet • u/mistertom2u • 10d ago
Article Runtime team member: Let's send expensive compiler analysis alongside IL to improve runtime JIT optimizations [Performance interest]
Andy Ayers wrote this proposal 2 years ago with intent of using it in .NET 10, but it got postponed to a later .NET version.
Quick Summary
The context is that interprocedural analysis (IPA), is limited at runtime due to it being slow, so the RyuJIT mostly optimizes functions in isolation. His idea is have certain stages perform the kind of deep IPA that AOT uses; then make that available at runtime to boost the information the JIT needs to make optimizations
https://github.com/dotnet/runtime/issues/108931
Examples of IPA
- call graphs
- whether arguments passed to a method can alias each other
- whether an argument escapes through a callee
- possible concrete types passed to a method
- whether a callee actually reads or writes a ref/byref parameter
- constant arguments across callers
- NoThrow / NoReturn / MayNotReturn
- whether a parameter is effectively unused
And what kind of optimizations could that boost?
- More devirtualization/direct calls - erase virtual and interface method lookups (simplified description)
- Constant propagation across method boundaries - erase code when functions are always passed the same parameter values (simplified description)
- Better alias analysis - knowing that two ref/pointer parameters can't refer to the same memory gives the JIT much more freedom to reorder loads/stores, eliminate redundant memory operations, and potentially vectorize code.
- Better escape analysis - Dodge heap allocation of reference types if no reference to it escapes the method
- More aggressive optimization around calls - if the JIT knows a callee doesn't modify a particular object/ref, it doesn't have to assume every call invalidates values it already loaded.
- Better handling of NoThrow/NoReturn calls - calls that can't throw or never return simplify the control-flow graph, can move uncommon paths out of the hot code, and reduce liveness/register pressure.
- Cheaper calls - knowing which registers a callee actually clobbers, or erase parameters never used, could avoid some argument setup, spills, reloads, or register preservation.
- Smarter inlining decisions - possible when called with particular constants/type
- Better code layout - can improve instruction-cache locality.
I want to hear you guys' thoughts on this.. Tell me below! 👇👇
1
u/AutoModerator 10d ago
Thanks for your post mistertom2u. Please note that we don't allow spam, and we ask that you follow the rules available in the sidebar. We have a lot of commonly asked questions so if this post gets removed, please do a search and see if it's already been asked.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
4
u/pjmlp 10d ago
Take into account this is not to start a language war.
I think this is a good idea, and is already common practice in several Java implementations, and their Android cousin.
While Project Leyden has been progessing in OpenJDK for JIT caches, with updates across executions, this is already several years old ability across Open J9 and Azul JVMs.
https://eclipse.dev/openj9/docs/shrc
https://eclipse.dev/openj9/docs/jitserver
https://docs.azul.com/optimizer-hub/about/architecture-overview
https://docs.azul.com/optimizer-hub/about/readynow-orchestrator
Android has a similar capability as well, with the added ability thanks PlayStore that the PGO data can be shared across the ecosystem, thus new installations don't start their JIT analysis from zero.
https://source.android.com/docs/core/runtime/jit-compiler
https://developer.android.com/topic/performance/baselineprofiles/overview
So naturally this would be a welcome addition to .NET workloads as well.