-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add property HardLimitBytes to GCMemoryInfo #25437
Conversation
This adds a new property HardLimitBytes. Unlike TotalAvailableMemoryBytes, this will reflect an explicitly set COMPLUS_GCHeapHardLimit. It will also reflect the fraction of a container's size that we use, where TotalAvailableMemoryBytes is the total container size. Normally, though, it is equal to TotalAvailableMemoryBytes. Fix #38821
I've tested this out and here are the results (my notes in parentheses): Running normally:
Running in a container:
Running with a hard limit :
|
I do not think we should be introducing this API. Instead, we should fix I do not think it makes sense for |
Added a new commit which makes |
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.
LGTM modulo nit.
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.
Actually, I have just notice that this value is used to convert highMemLoadThreshold
and lastRecordedMemLoad
to bytes. Is this conversion still valid?
You're right, in uint64_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
...
*memory_load = (uint32_t)((float)workingSetSize * 100.0 / (float)restricted_limit);
|
I think it would be best to return the numbers in bytes from the GC API, so there are no further adjustments needed in C#. |
@andy-ms
GetRestrictedPhysicalMemoryLimit() is not supposed to read what you set with the hardlimit configs. it's supposed to return the true physical memory limit for the process. |
+1 |
so we can compute highMemoryLoadThresholdBytes and memoryLoadBytes
src/gc/gc.cpp
Outdated
*highMemLoadThresholdBytes = (uint64_t) (((double)gc_heap::high_memory_load_th) / 100 * gc_heap::total_physical_mem); | ||
*totalAvailableMemoryBytes = gc_heap::heap_hard_limit != 0 ? gc_heap::heap_hard_limit : gc_heap::total_physical_mem; | ||
*lastRecordedMemLoadBytes = (uint64_t) (((double)gc_heap::last_gc_memory_load) / 100 * gc_heap::total_physical_mem); | ||
*lastRecordedMemLoadPct = gc_heap::last_gc_memory_load; |
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.
Is the last_gc_memory_load
relative to total_physical_mem
; or relative to *totalAvailableMemoryBytes
?
Either this or the previous line looks wrong. When memory load is 100%
, I think loadBytes
should be equal totalAvailableMemoryBytes
.
It may be best to get rid of the *lastRecordedMemLoadPct
argument, and just divide lastRecordedMemLoadBytes
and totalAvailableMemoryBytes
on the CoreLib side.
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.
last_gc_memory_load is relative to total_physical_mem. the problem is we do have a GetMemoryLoad that needs the actual memory load.
I would really prefer our original API proposal that had both the total physical memory and the hardlimit for the GC heap 'cause we do care about both. and I don't think it's unreasonable for the users to know about both. either that or we have another API (internal) that gets the actual physical memory load.
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.
last_gc_memory_load
is relative to total_physical_mem
. If totalAvailableMemoryBytes
is less than total_physical_mem
, then it will be impossible for last_gc_memory_load
to ever reach 100% -- it would be at most totalAvailableMemoryBytes / total_physical_mem * 100
.
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.
The primary reason why we have introduced the GCMemoryInfo
API is to allow folks to build self-tuning caches.
Could you please review the places where this API is used currently (our TlsOverPerCoreLockedStacksArrayPool
in particular - not sure whether there is anything else) and validate that it works correctly in all cases (regular VMs, containers, hardlimit)?
The current code for this is here:
coreclr/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
Line 303 in 4ca032d
GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); |
GetMemoryLoad
that needs the actual memory load.
This is used by the internal experimental MemoryLoadChangeNotification
APIs. Is this API used by anything?
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.
the impl in TlsOverPerCoreLockedStacksArrayPool.cs looks correct - it could be improved (for example it could say only trim if the size it trims could actually make a difference in memory pressure) but I don't think we are doing that for 3.0.
it seems awkward to have to fit the 2 numbers (actual physical memory and hardlimit) into 1. and if we don't want to have to call the diagnostics API to retrieve the physical memory (which also seems awkward for GC) our original proposal seems more sensible to me.
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.
What logic do we think would need to look at physical memory rather than the hard limit?
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.
We have received a lot of feedback in 2.x about ArrayPool keeping too much memory over time. We have seen people taking extreme measures like poking the ArrayPool via private reflection to make it release the memory. We have implemented the trimming at the last minute to mitigate the situation.
If the ArrayPool trimming does not take the hardlimit into account, I expect that we are going to hear the same feedback again from folks who try to use the hardlimit.
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.
Right I'm agreeing with your assessment. I'm mostly wondering when you'd need the actual physical memory vs the hard limit (the other should probably be exposed where we have the other process specific APIs Process.*)
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.
(My reply was for Maoni's comment.)
I agree that the actual physical memory does not seem to be necessary to build decent trimming heuristic.
I think that there are 4 numbers to feed into a decent basic trimming heuristic:
- The GC heap size: current value and limit that it should stay under. I believe it is
HeapSizeBytes
andTotalAvailableMemoryBytes
in the current API. - The memory load (ie the GC heap size plus everything else): current value and limit that it should stay under. I believe it is
MemoryLoadBytes
andHighMemoryLoadThresholdBytes
in the current API.
When either of these gets too close to its limit, it is a signal to trim caches.
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.
I have opened https://github.com/dotnet/coreclr/issues/25552 on ArrayPool not respecting GCHardLimit.
src/gc/gcinterface.h
Outdated
virtual void GetMemoryInfo(uint64_t* highMemLoadThresholdBytes, | ||
uint64_t* totalPhysicalMemoryBytes, | ||
uint64_t* lastRecordedMemLoadBytes, | ||
uint32_t* lastRecordedMemLoadPct, | ||
size_t* lastRecordedHeapSize, |
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.
Nit: Add Bytes
suffix to these two args; or delete it from the other args here.
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.
As I have said, it would be nice to get rid of the lastRecordedMemLoadPct
because of it is redudant piece of data and change MemoryLoadChangeNotification to use the public GCMemoryInfo
properties. I think it should be using MemoryLoadBytes / HighPressureThreshold
just like ArrayPool.
The MemoryLoadChangeNotification is internal experimental API that is not used by anything. As far as I can tell, it is broken current and nobody can use it for anything useful. So passing lastRecordedMemLoadPct
through may be confusing, but it is not actively harmful.
The rest of the changes looks like an improvement to me, so I would be ok with merging this (either as is; or with cleaned up lastRecordedMemLoadPct
).
Thank you everyone! |
Yes, I like the change. It is good to make intension explicit. |
size_t* lastRecordedFragmentationBytes) | ||
{ | ||
*highMemLoadThresholdBytes = (uint64_t) (((double)gc_heap::high_memory_load_th) / 100 * gc_heap::total_physical_mem); | ||
*totalAvailableMemoryBytes = gc_heap::heap_hard_limit != 0 ? gc_heap::heap_hard_limit : gc_heap::total_physical_mem; |
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.
Is it fine to potentially return heap_hard_limit
here, which could be larger than total_physical_mem
if inproperly configured?
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.
If you misconfigure the hard limit, the GC itself won't work quite right either. You get what you asked for. We are not trying to guess whether you meant it or not.
…tnet/coreclr#25437) * Add property HardLimitBytes to GCMemoryInfo This adds a new property HardLimitBytes. Unlike TotalAvailableMemoryBytes, this will reflect an explicitly set COMPLUS_GCHeapHardLimit. It will also reflect the fraction of a container's size that we use, where TotalAvailableMemoryBytes is the total container size. Normally, though, it is equal to TotalAvailableMemoryBytes. Fix dotnet/coreclr#38821 * Remove HardLimitBytes; have TotalAvailableMemoryBytes take on its behavior * Fix typos * Separate total_physical_mem and heap_hard_limit so we can compute highMemoryLoadThresholdBytes and memoryLoadBytes * Do more work in gc.cpp instead of Gc.cs * Consistently end names in "Bytes" Commit migrated from dotnet/coreclr@11137fb
This adds a new property HardLimitBytes.
Unlike TotalAvailableMemoryBytes,
this will reflect an explicitly set COMPLUS_GCHeapHardLimit.
It will also reflect the fraction of a container's size that we use,
where TotalAvailableMemoryBytes is the total container size.
Normally, though, it is equal to TotalAvailableMemoryBytes.
Fix dotnet/corefx#38821