-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
FOH (Frozen Object Heap) work items #76151
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue Details.NET 8.0
Potential good/bad ideas
bool same = ReferenceEquals(new string(new char[0]), new string(new char[0])); // true However, it's unlikely that it's worth the potential risks.
object o = myInt; will be optimized in codegen to: if ((uint)myInt < 100)
o = 0xBAADF00D (/*boxed zero on FOH*/ * myInt * 24
else
o = box(myInt) Same for booleans, etc. Can't find the related issue but AFAIR it raised some ECMA-related concerns (around
|
Same ideas have been discussed multiple times. E.g.: #7079 (comment) . They have been always punted so far. (These ideas are not FOH specific. They can be implemented without FOH by paying for one extra indirection that is a minuscule cost.) |
We ourselves have places we don't use Array.Empty because the use is relying on reference equality/inequality. |
Thanks, good to know, updated the item.
Right, it's just that since we now have FOH we don't need to fragmentate POH/SOH with that pinned cache. I agree that the idea is dangerous, although I still think it could be an opt-in feature for some large projects (e.g. for those who had to do it by hands today), the implementation should be relatively simple. |
Doubt it. Dangerous opt-ins do not work for large projects. It is impossible (prohibitively expensive) to audit their code to validate that it is safe to opt-in. |
fair enough! |
I'm curious how that is possible. If memory serves correctly, @jkotas stated on this issue tracker a long time ago that the policy is to have the empty string be a singleton. |
That is exactly what is written in that item, isn't? I just decided to put some ideas from the past to, probably, inspire for new ones. |
For example: var s = string.Copy("");
Console.WriteLine(s == "");
Console.WriteLine(ReferenceEquals(s, "")); |
Ah, the question was "how is it possible to create a unique empty string" and I read it as "how the optimization you propose is possible" 🙂 |
Idea: try to reserve memory for the first segment (4mb) next to coreclr in memory, the way we do currently for 4Gb for loader heap, I guess it won't hurt if we first try to reserve 4mb and then those 4Gb. |
@jkotas am I correct that we can handle If we're in a static constructor and see pattern like
we can replace ah, I assume only if constructor doesn't touch that field any more (doesn't try to set it multiple times) |
Same for empty (or any size) arrays:
so we won't have to special-case Array.Empty. So the strategy will be:
Or actually, as an initial impl, it can be just:
|
You can do this for non-collectible code only. Also, you can do the same thing for arrays of length 0. It would cover the Array.Empty case without special casing.
I am not sure what you mean by this. |
I meant that if a static constructor is complicated then
pattern might be set in a loop so we'll end up allocating immortal frozen object each iteration, etc. So I mean that if we see
then it's definitely just a simple cctor that only sets one field 🙂 - I meant it as an initial quick/safe implementation. Array.Empty falls into that category |
Idea: make pinning no-op for frozen objects since we plan to move |
Idea: provide higher alignment for arrays on FOH in order to achieve higher perf with SIMD operations. |
Definitely will require changes on GC side where gc assumes fixed alignment for all objects when it iterates them, e.g: void gc_heap::seg_set_mark_bits (heap_segment* seg)
{
uint8_t* o = heap_segment_mem (seg);
while (o < heap_segment_allocated (seg))
{
set_marked (o);
o = o + Align (size(o));
}
} |
I would also say that this is very niche. Note that the embedding of the object references from readonly fields can be also extended to pinned objects allocated on POH.
|
@jkotas do I understand it correctly that in order to detect that, I need to add an additional argument to my custom alloc helper - Basically, call However, it's probably easier to just pass cctor's type handle to that helper UPD: we don't support tiered compilation for collectible assemblies so in theory we can emit custom allocs only for Tier1 (promoted from tier0). But that might be not future-proof. |
Stack crawling to find caller is an anti-pattern. You should do it all at JIT time, something like:
|
Can't R2R assemblies be loaded into collectible ALCs? |
They cannot today: runtime/src/coreclr/vm/readytoruninfo.cpp Line 527 in fec0221
|
@jkotas @Maoni0 you asked for numbers - I downloaded BingSNR, updated to net8 and ran its benchmarks locally with bombardier (that simulates load): And here are the general performance counters:
My env vars in
NOTE: I commented GCName to enable gc regions (just to test net8). |
Should we double the frozen segment reservations (ie 4mb, 8mb, 16mb, ...) to have a fewer of these? |
Makes sense! Btw, it's impressive how much GC Regions save for the service, GC Heap with regions: 2Gb, without 3.5-4.5Gb |
I do not think that Bing is disabling R2R. Was there a particular reason you have disabled it? |
It was an unrelated experiment - just wanted to see how much methods it jits in total, the biggest number I saw was 238k methods (21.8Mb IL Jitted) - now plan to check the actual size of native code in memory. |
thanks for the data!
♥
exactly, you don't need the GC support for this. fewer FOH segs would be good. |
More insights from the bing service - among those 17Mb of objects in the FOH, 9mb of them are RuntimeType and 8mb are string literals, the longest string literal is 24kb |
So... curious... Some sort of lookup table? |
The content looks like a huge set of coordinates (comma-separated floating points) for that one 🙂 |
I'm familiar with this service, and the FOH should be significantly larger than 17MB, it should be in the order of ~800MB or so. It contains all the RESX strings, and significant other object graphs. Perhaps we can chat offline to see why you're not seeing that size. One question I did have is, will this collection of work regress performance of file-backed segments in the FOH? |
I was only counting objects in FOH segments added by runtime. Potentially, for your service we can surface a configuration knob to set the initial size e.g. 20Mb to have only just one extra frozen segment (currently it's 3).
I'd not expect any regressions. Perhaps, only if you're not on GC Regions as we had to enable scanning for in-range FOH segments there (#76251) |
All items except dotnet/diagnostics#4156 are closed, so closing it for .NET 8.0 and leave dotnet/diagnostics#4156 open (working on it now) |
A tracking issue for FOH-related tasks (Frozen Object Heap). It's a special heap for immortal objects such as string literals, Type objects, etc (see the list below). Conceptually similar to POH but it doesn't have a public API thus it never contains short-living objects + there are some relaxations in GC for FOH. It provides two advantages:
.NET 8.0
static readonly
fields holding frozen objects to const handles JIT: import static readonly fields holding frozen objects as const handles #76112static readonly
fields on FOH e.g.static readonly object SyncObj = new();
and arrays Allocate Array.Empty<> on a frozen segment (NonGC heap) #85559Suggestions are very welcome!
category:planning
theme:memory-usage
skill-level:expert
cost:medium
impact:medium
The text was updated successfully, but these errors were encountered: