-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Remove lazy initialized string literals #122076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
cc @dotnet/jit-contrib @jkotas opinions? (I guess I can also remove |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
|
I expect that this change will regress working set since we are going to allocate memory for these strings eagerly. Do you have an estimate for what this regression is going to be for a typical real-world app?
This can be made lock-free if we cared.
Yes, and the CORINFO_HELP_STRCNS helper. (Note that this optimization is not enabled in crossgen2 - #48580.) |
|
Code LGTM. Does this meaningfully increase app startup time or time spent in the JIT itself? It seems like it would, but maybe not to an extent that matters. |
Sure, but still will be a bit less efficient than just a frozen object (it also unlocks other constant folding optimizations that we might run even in cold blocks). Probably still makes sense to optimize the VM if someone screams at us for poor String.Intern performance, e.g. https://sergeyteplyakov.github.io/Blog/benchmarking/2023/12/10/Intern_or_Not_Intern.html:
The main concern here is an increased working set as the JIT now has to allocate string objects even for cold blocks (it might eventually be fixed by a 'partial compilation' feature if it ever lands for optimized code) - it should be relatively quick using FrozenHeapManager, but it's very unlikely we're talking about a huge impact. It seems that when string interpolation syntax is used - it's not impacted as all string-interpolation logic is happening outside of BBJ_THROW. |
Depends on what you are measuring. We typically avoid regressing non-exceptional paths to make exception throwing faster. Frozen string allocation is doing the opposite. |
|
(I am ok with taking this change as a simplification that comes with a small regression.) |
Is the increase in working set a one-time cost, or does it grow with each string allocated in frozen heap? |
it's one time, all string literals are registered in a global interning table. It's actually doesn't matter if it's frozen or not (frozen saves a bit of space by not occupying pinned handles).
I'll analyze the impact on a big app, but I'm pretty sure the impact will be negligible simply because all localizeable (SR.*) or interned strings aren't impacted, so only raw string literals in BBJ_THROW blocks are, as you can see from the removed code, we never actually enabled the lazy helpers for all cold blocks. |
This seems like something that would be beneficial to look into. Even with this PR we won't always use the FOH (such as for unloadable assemblies) and locking the |
|
Would it make sense to not intern string literals when the string is known to be used in a |
It would be a breaking change. |
|
@jkotas so I analyzed a few local apps and I was not able to detect any visible working set size regression because it was within noise, but I did noticed extra objects in the frozen heap, e.g. OrchardCMS: Main: PR: so extra 45kb of working set (after 120sec of bombardier load) for an app that occupies ~1GB of working set (so +0.005%) The Foh numbers were stable unlike all other generations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR removes the lazy string literal initialization optimization to improve performance by eliminating lock contention in the CORINFO_HELP_STRCNS helper. The optimization was previously used in specific scenarios like throw blocks, but the lock overhead outweighed the benefits of avoiding allocations on the frozen heap.
- Removes the
CORINFO_HELP_STRCNSJIT helper and all associated infrastructure - Eliminates the
getLazyStringLiteralHelperJIT interface method - Simplifies exception throwing code by removing the lazy initialization path in
fgMorphConst
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/qcallentrypoints.cpp | Removes QCall entry point for String_StrCns |
| src/coreclr/vm/jitinterface.cpp | Removes getLazyStringLiteralHelper implementation and ReadyToRun helper mapping |
| src/coreclr/vm/corelib.h | Removes STRCNS method definition macro |
| src/coreclr/vm/appdomainnative.hpp | Removes String_StrCns function declaration |
| src/coreclr/vm/appdomainnative.cpp | Removes String_StrCns QCall implementation |
| src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp | Removes superpmi wrapper for getLazyStringLiteralHelper |
| src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp | Removes shim-simple interceptor |
| src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp | Removes shim-counter interceptor |
| src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp | Removes shim-collector interceptor |
| src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h | Comments out packet enum and removes method declarations |
| src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp | Removes recording/replay methods for getLazyStringLiteralHelper |
| src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h | Removes GetLazyStringLiteralHelper lightweight map entry |
| src/coreclr/tools/aot/jitinterface/jitinterface_generated.h | Removes callback function pointer and wrapper method |
| src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt | Removes getLazyStringLiteralHelper from thunk input |
| src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs | Reduces callback array from 178 to 177 and shifts indices down after removal |
| src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | Removes getLazyStringLiteralHelper stub implementation |
| src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs | Removes CORINFO_HELP_STRCNS enum value |
| src/coreclr/jit/valuenumfuncs.h | Removes VNF_LazyStrCns value number function |
| src/coreclr/jit/valuenum.cpp | Removes CORINFO_HELP_STRCNS to VNF_LazyStrCns mapping |
| src/coreclr/jit/utils.cpp | Removes CORINFO_HELP_STRCNS helper properties |
| src/coreclr/jit/morph.cpp | Removes entire lazy string construction logic in fgMorphConst |
| src/coreclr/jit/compiler.hpp | Removes CORINFO_HELP_STRCNS from IsSharedStaticHelper check |
| src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp | Removes wrapper method |
| src/coreclr/jit/ICorJitInfo_names_generated.h | Removes API name entry |
| src/coreclr/inc/readytorun.h | Marks READYTORUN_HELPER_GetString as no longer supported in v17.0 |
| src/coreclr/inc/jithelpers.h | Removes CORINFO_HELP_STRCNS dynamic helper entry |
| src/coreclr/inc/jiteeversionguid.h | Updates JIT-EE version GUID to reflect interface change |
| src/coreclr/inc/icorjitinfoimpl_generated.h | Removes method declaration |
| src/coreclr/inc/corinfo.h | Removes CORINFO_HELP_STRCNS enum and getLazyStringLiteralHelper interface method |
| src/coreclr/System.Private.CoreLib/src/System/String.CoreCLR.cs | Removes StrCns and StrCnsInternal managed methods |
| docs/design/coreclr/botr/readytorun-format.md | Updates documentation to mark GetString as unused since v17.0 |
It seems it's not worth it to avoid allocations on the frozen heap - the lazy string helper has a lock inside (Crst in GetStringLiteral) and it badly impacts performance for
throw Exception(<string literal>)code (as was reported internally) especially under contention.; Method Program:Foo(bool) (FullOpts) push rbx sub rsp, 32 mov rcx, 0x7FFBD79E2E10 ; System.Exception call CORINFO_HELP_NEWSFAST mov rbx, rax - mov ecx, 1 - mov rdx, 0x7FFBD7FE0000 - call [CORINFO_HELP_STRCNS] - mov rdx, rax mov rcx, rbx + mov rdx, 0x28922695108 ; 'Hello' call [System.Exception:.ctor(System.String):this] mov rcx, rbx call CORINFO_HELP_THROW int3 -; Total bytes of code: 65 +; Total bytes of code: 51