From 92559e7b09aa825feaa17443acdfbeb2c8600c1f Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Mon, 7 Apr 2025 00:20:05 -0400 Subject: [PATCH 1/4] Use ConditionalWeakTable ref struct enumerator to avoid allocation in ArrayPool finalizer. --- .../src/System/Buffers/SharedArrayPool.cs | 6 +- .../CompilerServices/ConditionalWeakTable.cs | 61 ++++++++++++++++--- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/SharedArrayPool.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/SharedArrayPool.cs index 89f32fe337f161..7a91aef50cf3f5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/SharedArrayPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/SharedArrayPool.cs @@ -209,14 +209,14 @@ public bool Trim() { if (!log.IsEnabled()) { - foreach (KeyValuePair tlsBuckets in _allTlsBuckets) + foreach (KeyValuePair tlsBuckets in _allTlsBuckets.EnumerateOnStack()) { Array.Clear(tlsBuckets.Key); } } else { - foreach (KeyValuePair tlsBuckets in _allTlsBuckets) + foreach (KeyValuePair tlsBuckets in _allTlsBuckets.EnumerateOnStack()) { SharedArrayPoolThreadLocalArray[] buckets = tlsBuckets.Key; for (int i = 0; i < buckets.Length; i++) @@ -241,7 +241,7 @@ public bool Trim() _ => 30_000, }; - foreach (KeyValuePair tlsBuckets in _allTlsBuckets) + foreach (KeyValuePair tlsBuckets in _allTlsBuckets.EnumerateOnStack()) { SharedArrayPoolThreadLocalArray[] buckets = tlsBuckets.Key; for (int i = 0; i < buckets.Length; i++) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs index eb6af90b5a5043..1dfd43f9c01e5a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConditionalWeakTable.cs @@ -369,8 +369,8 @@ IEnumerator> IEnumerable>. IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); - /// Provides an enumerator for the table. - private sealed class Enumerator : IEnumerator> + /// The core implementation for the enumerators for the table. + private struct EnumeratorCore : IDisposable { // The enumerator would ideally hold a reference to the Container and the end index within that // container. However, the safety of the CWT depends on the only reference to the Container being @@ -393,7 +393,7 @@ private sealed class Enumerator : IEnumerator> private int _currentIndex; // the current index into the container private KeyValuePair _current; // the current entry set by MoveNext and returned from Current - public Enumerator(ConditionalWeakTable table) + public EnumeratorCore(ConditionalWeakTable table) { Debug.Assert(table != null, "Must provide a valid table"); Debug.Assert(Monitor.IsEntered(table._lock), "Must hold the _lock lock to construct the enumerator"); @@ -410,11 +410,6 @@ public Enumerator(ConditionalWeakTable table) _currentIndex = -1; } - ~Enumerator() - { - Dispose(); - } - public void Dispose() { // Use an interlocked operation to ensure that only one thread can get access to @@ -485,6 +480,56 @@ public KeyValuePair Current return _current; } } + } + + /// Provides an enumerator for the table. + private sealed class Enumerator : IEnumerator> + { + private EnumeratorCore _enumeratorCore; + + public Enumerator(ConditionalWeakTable table) => _enumeratorCore = new(table); + + ~Enumerator() + { + Dispose(); + } + + public void Dispose() => _enumeratorCore.Dispose(); + + public bool MoveNext() => _enumeratorCore.MoveNext(); + + public KeyValuePair Current => _enumeratorCore.Current; + + object? IEnumerator.Current => Current; + + public void Reset() { } + } + + /// Gets an enumerable used to enumerate the values on the stack without allocation. + // This API is unsafe to use if the user neglects to call Dispose, which is why + // we can only use this internally where we can enforce proper foreach usage. + // It's also unsafe to use in async functions, even with proper foreach, because there's + // a chance that the async will never continue, in which case we need to rely on the finalizer in the regular enumerator. + internal RefEnumerable EnumerateOnStack() => new RefEnumerable(this); + + /// Provides an enumerable used to enumerate the values on the stack without allocation. + internal readonly ref struct RefEnumerable(ConditionalWeakTable table) + { + public RefEnumerator GetEnumerator() => new(table); + } + + /// Provides an enumerator for the table that can be used on the stack without allocation. + internal ref struct RefEnumerator : IEnumerator> + { + private EnumeratorCore _enumeratorCore; + + public RefEnumerator(ConditionalWeakTable table) => _enumeratorCore = new(table); + + public void Dispose() => _enumeratorCore.Dispose(); + + public bool MoveNext() => _enumeratorCore.MoveNext(); + + public KeyValuePair Current => _enumeratorCore.Current; object? IEnumerator.Current => Current; From 116083dec754cabbef36109ebe9c41cb844b08ec Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Mon, 7 Apr 2025 02:07:41 -0400 Subject: [PATCH 2/4] Remove most allocations in `GC.GetGCMemoryInfo`. --- .../src/System/GC.CoreCLR.cs | 6 +- .../src/System/GCMemoryInfo.cs | 98 +++++++++++++------ 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index 184ce80f44b899..5cc0ed7b3dfcbe 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -62,6 +62,10 @@ public static partial class GC [MethodImpl(MethodImplOptions.InternalCall)] private static extern void GetMemoryInfo(GCMemoryInfoData data, int kind); + // Used to avoid allocating in GetGCMemoryInfo after the first call. + [ThreadStatic] + private static GCMemoryInfoData? t_gCMemoryInfoData; + /// Gets garbage collection memory information. /// An object that contains information about the garbage collector's memory usage. public static GCMemoryInfo GetGCMemoryInfo() => GetGCMemoryInfo(GCKind.Any); @@ -80,7 +84,7 @@ public static GCMemoryInfo GetGCMemoryInfo(GCKind kind) GCKind.Background)); } - var data = new GCMemoryInfoData(); + var data = t_gCMemoryInfoData ??= new GCMemoryInfoData(); GetMemoryInfo(data, (int)kind); return new GCMemoryInfo(data); } diff --git a/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs b/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs index 59cce34d7d4038..f4a3e47033e829 100644 --- a/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs @@ -71,18 +71,14 @@ internal sealed class GCMemoryInfoData internal byte _compacted; internal byte _concurrent; - private GCGenerationInfo _generationInfo0; - private GCGenerationInfo _generationInfo1; - private GCGenerationInfo _generationInfo2; - private GCGenerationInfo _generationInfo3; - private GCGenerationInfo _generationInfo4; - - internal ReadOnlySpan GenerationInfoAsSpan => MemoryMarshal.CreateReadOnlySpan(ref _generationInfo0, 5); - - private TimeSpan _pauseDuration0; - private TimeSpan _pauseDuration1; - - internal ReadOnlySpan PauseDurationsAsSpan => MemoryMarshal.CreateReadOnlySpan(ref _pauseDuration0, 2); + internal GCGenerationInfo _generationInfo0; + internal GCGenerationInfo _generationInfo1; + internal GCGenerationInfo _generationInfo2; + internal GCGenerationInfo _generationInfo3; + internal GCGenerationInfo _generationInfo4; + + internal TimeSpan _pauseDuration0; + internal TimeSpan _pauseDuration1; } /// Provides a set of APIs that can be used to retrieve garbage collection information. @@ -96,22 +92,64 @@ internal sealed class GCMemoryInfoData /// public readonly struct GCMemoryInfo { - private readonly GCMemoryInfoData _data; + private readonly long _highMemoryLoadThresholdBytes; + private readonly long _totalAvailableMemoryBytes; + private readonly long _memoryLoadBytes; + private readonly long _heapSizeBytes; + private readonly long _fragmentedBytes; + private readonly long _totalCommittedBytes; + private readonly long _promotedBytes; + private readonly long _pinnedObjectsCount; + private readonly long _finalizationPendingCount; + private readonly long _index; + private readonly int _generation; + private readonly int _pauseTimePercentage; + private readonly byte _compacted; + private readonly byte _concurrent; + + private readonly GCGenerationInfo _generationInfo0; + private readonly GCGenerationInfo _generationInfo1; + private readonly GCGenerationInfo _generationInfo2; + private readonly GCGenerationInfo _generationInfo3; + private readonly GCGenerationInfo _generationInfo4; + + private readonly TimeSpan _pauseDuration0; + private readonly TimeSpan _pauseDuration1; internal GCMemoryInfo(GCMemoryInfoData data) { - _data = data; + _highMemoryLoadThresholdBytes = data._highMemoryLoadThresholdBytes; + _totalAvailableMemoryBytes = data._totalAvailableMemoryBytes; + _memoryLoadBytes = data._memoryLoadBytes; + _heapSizeBytes = data._heapSizeBytes; + _fragmentedBytes = data._fragmentedBytes; + _totalCommittedBytes = data._totalCommittedBytes; + _promotedBytes = data._promotedBytes; + _pinnedObjectsCount = data._pinnedObjectsCount; + _finalizationPendingCount = data._finalizationPendingCount; + _index = data._index; + _generation = data._generation; + _pauseTimePercentage = data._pauseTimePercentage; + _compacted = data._compacted; + _concurrent = data._concurrent; + _generationInfo0 = data._generationInfo0; + _generationInfo1 = data._generationInfo1; + _generationInfo2 = data._generationInfo2; + _generationInfo3 = data._generationInfo3; + _generationInfo4 = data._generationInfo4; + _pauseDuration0 = data._pauseDuration0; + _pauseDuration1 = data._pauseDuration1; } /// /// High memory load threshold when this GC occurred /// - public long HighMemoryLoadThresholdBytes => _data._highMemoryLoadThresholdBytes; + public long HighMemoryLoadThresholdBytes => _highMemoryLoadThresholdBytes; /// /// Memory load when this GC occurred /// - public long MemoryLoadBytes => _data._memoryLoadBytes; + public long MemoryLoadBytes => _memoryLoadBytes; /// /// Total available memory for the GC to use when this GC occurred. @@ -121,12 +159,12 @@ internal GCMemoryInfo(GCMemoryInfoData data) /// If the program is run in a container, this will be an implementation-defined fraction of the container's size. /// Else, this is the physical memory on the machine that was available for the GC to use when this GC occurred. /// - public long TotalAvailableMemoryBytes => _data._totalAvailableMemoryBytes; + public long TotalAvailableMemoryBytes => _totalAvailableMemoryBytes; /// /// The total heap size when this GC occurred /// - public long HeapSizeBytes => _data._heapSizeBytes; + public long HeapSizeBytes => _heapSizeBytes; /// /// The total fragmentation when this GC occurred @@ -140,64 +178,64 @@ internal GCMemoryInfo(GCMemoryInfoData data) /// The memory between OBJ_A and OBJ_D marked `F` is considered part of the FragmentedBytes, and will be used to allocate new objects. The memory after OBJ_D will not be /// considered part of the FragmentedBytes, and will also be used to allocate new objects /// - public long FragmentedBytes => _data._fragmentedBytes; + public long FragmentedBytes => _fragmentedBytes; /// /// The index of this GC. GC indices start with 1 and get increased at the beginning of a GC. /// Since the info is updated at the end of a GC, this means you can get the info for a BGC /// with a smaller index than a foreground GC finished earlier. /// - public long Index => _data._index; + public long Index => _index; /// /// The generation this GC collected. Collecting a generation means all its younger generation(s) /// are also collected. /// - public int Generation => _data._generation; + public int Generation => _generation; /// /// Is this a compacting GC or not. /// - public bool Compacted => _data._compacted != 0; + public bool Compacted => _compacted != 0; /// /// Is this a concurrent GC (BGC) or not. /// - public bool Concurrent => _data._concurrent != 0; + public bool Concurrent => _concurrent != 0; /// /// Total committed bytes of the managed heap. /// - public long TotalCommittedBytes => _data._totalCommittedBytes; + public long TotalCommittedBytes => _totalCommittedBytes; /// /// Promoted bytes for this GC. /// - public long PromotedBytes => _data._promotedBytes; + public long PromotedBytes => _promotedBytes; /// /// Number of pinned objects this GC observed. /// - public long PinnedObjectsCount => _data._pinnedObjectsCount; + public long PinnedObjectsCount => _pinnedObjectsCount; /// /// Number of objects ready for finalization this GC observed. /// - public long FinalizationPendingCount => _data._finalizationPendingCount; + public long FinalizationPendingCount => _finalizationPendingCount; /// /// Pause durations. For blocking GCs there's only 1 pause; for BGC there are 2. /// - public ReadOnlySpan PauseDurations => _data.PauseDurationsAsSpan; + public ReadOnlySpan PauseDurations => MemoryMarshal.CreateReadOnlySpan(in _pauseDuration0, 2); /// /// This is the % pause time in GC so far. If it's 1.2%, this number is 1.2. /// - public double PauseTimePercentage => (double)_data._pauseTimePercentage / 100.0; + public double PauseTimePercentage => (double)_pauseTimePercentage / 100.0; /// /// Generation info for all generations. /// - public ReadOnlySpan GenerationInfo => _data.GenerationInfoAsSpan; + public ReadOnlySpan GenerationInfo => MemoryMarshal.CreateReadOnlySpan(in _generationInfo0, 5); } } From 23b3064fe977c9bfa95f765211c15235f787ac57 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Mon, 7 Apr 2025 14:17:33 -0400 Subject: [PATCH 3/4] Pass pointer to struct instead of class. --- .../src/System/GC.CoreCLR.cs | 14 +++++--------- .../src/System/GCMemoryInfo.cs | 2 -- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index 5cc0ed7b3dfcbe..93c4903b41ae99 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -60,11 +60,7 @@ public enum GCNotificationStatus public static partial class GC { [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void GetMemoryInfo(GCMemoryInfoData data, int kind); - - // Used to avoid allocating in GetGCMemoryInfo after the first call. - [ThreadStatic] - private static GCMemoryInfoData? t_gCMemoryInfoData; + private static extern unsafe void GetMemoryInfo(GCMemoryInfo* data, int kind); /// Gets garbage collection memory information. /// An object that contains information about the garbage collector's memory usage. @@ -73,7 +69,7 @@ public static partial class GC /// Gets garbage collection memory information. /// The kind of collection for which to retrieve memory information. /// An object that contains information about the garbage collector's memory usage. - public static GCMemoryInfo GetGCMemoryInfo(GCKind kind) + public static unsafe GCMemoryInfo GetGCMemoryInfo(GCKind kind) { if ((kind < GCKind.Any) || (kind > GCKind.Background)) { @@ -84,9 +80,9 @@ public static GCMemoryInfo GetGCMemoryInfo(GCKind kind) GCKind.Background)); } - var data = t_gCMemoryInfoData ??= new GCMemoryInfoData(); - GetMemoryInfo(data, (int)kind); - return new GCMemoryInfo(data); + GCMemoryInfo data = default; + GetMemoryInfo(&data, (int)kind); + return data; } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_StartNoGCRegion")] diff --git a/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs b/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs index f4a3e47033e829..7087c75d881c20 100644 --- a/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs @@ -106,13 +106,11 @@ public readonly struct GCMemoryInfo private readonly int _pauseTimePercentage; private readonly byte _compacted; private readonly byte _concurrent; - private readonly GCGenerationInfo _generationInfo0; private readonly GCGenerationInfo _generationInfo1; private readonly GCGenerationInfo _generationInfo2; private readonly GCGenerationInfo _generationInfo3; private readonly GCGenerationInfo _generationInfo4; - private readonly TimeSpan _pauseDuration0; private readonly TimeSpan _pauseDuration1; From 5c221784dee83784902873fd1c8d25f8ff324eaf Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Mon, 7 Apr 2025 15:55:17 -0400 Subject: [PATCH 4/4] Revert GCMemoryInfo and change GCMemoryInfoData to struct. Adjust cpp impls to take struct pointers. --- .../src/System/GC.CoreCLR.cs | 6 +- .../src/System/GC.NativeAot.cs | 6 +- .../src/System/Runtime/RuntimeImports.cs | 2 +- src/coreclr/vm/comutilnative.cpp | 36 ++++--- src/coreclr/vm/comutilnative.h | 12 +-- .../src/System/GCMemoryInfo.cs | 96 ++++++------------- .../src/System/GC.Mono.cs | 2 +- 7 files changed, 57 insertions(+), 103 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index 93c4903b41ae99..0c88b7ada8e0c4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -60,7 +60,7 @@ public enum GCNotificationStatus public static partial class GC { [MethodImpl(MethodImplOptions.InternalCall)] - private static extern unsafe void GetMemoryInfo(GCMemoryInfo* data, int kind); + private static extern unsafe void GetMemoryInfo(GCMemoryInfoData* data, int kind); /// Gets garbage collection memory information. /// An object that contains information about the garbage collector's memory usage. @@ -80,9 +80,9 @@ public static unsafe GCMemoryInfo GetGCMemoryInfo(GCKind kind) GCKind.Background)); } - GCMemoryInfo data = default; + GCMemoryInfoData data = default; GetMemoryInfo(&data, (int)kind); - return data; + return new GCMemoryInfo(data); } [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_StartNoGCRegion")] diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs index 67d09f8b246a6d..3905b7f3e74b2d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/GC.NativeAot.cs @@ -764,7 +764,7 @@ public static long GetTotalAllocatedBytes(bool precise = false) /// Gets garbage collection memory information. /// The kind of collection for which to retrieve memory information. /// An object that contains information about the garbage collector's memory usage. - public static GCMemoryInfo GetGCMemoryInfo(GCKind kind) + public static unsafe GCMemoryInfo GetGCMemoryInfo(GCKind kind) { if ((kind < GCKind.Any) || (kind > GCKind.Background)) { @@ -775,8 +775,8 @@ public static GCMemoryInfo GetGCMemoryInfo(GCKind kind) GCKind.Background)); } - var data = new GCMemoryInfoData(); - RuntimeImports.RhGetMemoryInfo(ref data.GetRawData(), kind); + GCMemoryInfoData data = default; + RuntimeImports.RhGetMemoryInfo(&data, kind); return new GCMemoryInfo(data); } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index 4fe7ee2e706106..bf140a03c69a75 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -241,7 +241,7 @@ internal struct GCHeapHardLimitInfo [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhGetMemoryInfo")] - internal static extern void RhGetMemoryInfo(ref byte info, GCKind kind); + internal static extern unsafe void RhGetMemoryInfo(GCMemoryInfoData* data, GCKind kind); [LibraryImport(RuntimeLibrary)] internal static unsafe partial void RhAllocateNewArray(MethodTable* pArrayEEType, uint numElements, uint flags, void* pResult); diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index d039226c328cad..031cb04bff2d9f 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -584,32 +584,30 @@ FCIMPL0(INT64, GCInterface::GetTotalPauseDuration) } FCIMPLEND -FCIMPL2(void, GCInterface::GetMemoryInfo, Object* objUNSAFE, int kind) +FCIMPL2(void, GCInterface::GetMemoryInfo, GCMemoryInfoData* pData, int kind) { FCALL_CONTRACT; FC_GC_POLL_NOT_NEEDED(); - GCMEMORYINFODATAREF objGCMemoryInfo = (GCMEMORYINFODATAREF)(ObjectToOBJECTREF (objUNSAFE)); - - UINT64* genInfoRaw = (UINT64*)&(objGCMemoryInfo->generationInfo0); - UINT64* pauseInfoRaw = (UINT64*)&(objGCMemoryInfo->pauseDuration0); + UINT64* genInfoRaw = (UINT64*)&(pData->generationInfo0); + UINT64* pauseInfoRaw = (UINT64*)&(pData->pauseDuration0); return GCHeapUtilities::GetGCHeap()->GetMemoryInfo( - &(objGCMemoryInfo->highMemLoadThresholdBytes), - &(objGCMemoryInfo->totalAvailableMemoryBytes), - &(objGCMemoryInfo->lastRecordedMemLoadBytes), - &(objGCMemoryInfo->lastRecordedHeapSizeBytes), - &(objGCMemoryInfo->lastRecordedFragmentationBytes), - &(objGCMemoryInfo->totalCommittedBytes), - &(objGCMemoryInfo->promotedBytes), - &(objGCMemoryInfo->pinnedObjectCount), - &(objGCMemoryInfo->finalizationPendingCount), - &(objGCMemoryInfo->index), - &(objGCMemoryInfo->generation), - &(objGCMemoryInfo->pauseTimePercent), - (bool*)&(objGCMemoryInfo->isCompaction), - (bool*)&(objGCMemoryInfo->isConcurrent), + &(pData->highMemLoadThresholdBytes), + &(pData->totalAvailableMemoryBytes), + &(pData->lastRecordedMemLoadBytes), + &(pData->lastRecordedHeapSizeBytes), + &(pData->lastRecordedFragmentationBytes), + &(pData->totalCommittedBytes), + &(pData->promotedBytes), + &(pData->pinnedObjectCount), + &(pData->finalizationPendingCount), + &(pData->index), + &(pData->generation), + &(pData->pauseTimePercent), + (bool*)&(pData->isCompaction), + (bool*)&(pData->isConcurrent), genInfoRaw, pauseInfoRaw, kind); diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index 1f5fda9ed02ccc..cf8cc472622dae 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -101,7 +101,7 @@ struct GCGenerationInfo }; #include "pshpack4.h" -class GCMemoryInfoData : public Object +struct GCMemoryInfoData { public: UINT64 highMemLoadThresholdBytes; @@ -131,14 +131,6 @@ class GCMemoryInfoData : public Object }; #include "poppack.h" -#ifdef USE_CHECKED_OBJECTREFS -typedef REF GCMEMORYINFODATA; -typedef REF GCMEMORYINFODATAREF; -#else // USE_CHECKED_OBJECTREFS -typedef GCMemoryInfoData * GCMEMORYINFODATA; -typedef GCMemoryInfoData * GCMEMORYINFODATAREF; -#endif // USE_CHECKED_OBJECTREFS - using EnumerateConfigurationValuesCallback = void (*)(void* context, void* name, void* publicKey, GCConfigurationType type, int64_t data); struct GCHeapHardLimitInfo @@ -166,7 +158,7 @@ class GCInterface { static FORCEINLINE UINT64 InterlockedSub(UINT64 *pMinuend, UINT64 subtrahend); static FCDECL0(INT64, GetTotalPauseDuration); - static FCDECL2(void, GetMemoryInfo, Object* objUNSAFE, int kind); + static FCDECL2(void, GetMemoryInfo, GCMemoryInfoData* pData, int kind); static FCDECL0(UINT32, GetMemoryLoad); static FCDECL0(int, GetGcLatencyMode); static FCDECL1(int, SetGcLatencyMode, int newLatencyMode); diff --git a/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs b/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs index 7087c75d881c20..0b1eba089cd1ca 100644 --- a/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/GCMemoryInfo.cs @@ -54,7 +54,7 @@ public enum GCKind }; [StructLayout(LayoutKind.Sequential)] - internal sealed class GCMemoryInfoData + internal struct GCMemoryInfoData { internal long _highMemoryLoadThresholdBytes; internal long _totalAvailableMemoryBytes; @@ -71,14 +71,18 @@ internal sealed class GCMemoryInfoData internal byte _compacted; internal byte _concurrent; - internal GCGenerationInfo _generationInfo0; - internal GCGenerationInfo _generationInfo1; - internal GCGenerationInfo _generationInfo2; - internal GCGenerationInfo _generationInfo3; - internal GCGenerationInfo _generationInfo4; + private GCGenerationInfo _generationInfo0; + private GCGenerationInfo _generationInfo1; + private GCGenerationInfo _generationInfo2; + private GCGenerationInfo _generationInfo3; + private GCGenerationInfo _generationInfo4; - internal TimeSpan _pauseDuration0; - internal TimeSpan _pauseDuration1; + internal ReadOnlySpan GenerationInfoAsSpan => MemoryMarshal.CreateReadOnlySpan(ref _generationInfo0, 5); + + private TimeSpan _pauseDuration0; + private TimeSpan _pauseDuration1; + + internal ReadOnlySpan PauseDurationsAsSpan => MemoryMarshal.CreateReadOnlySpan(ref _pauseDuration0, 2); } /// Provides a set of APIs that can be used to retrieve garbage collection information. @@ -92,62 +96,22 @@ internal sealed class GCMemoryInfoData /// public readonly struct GCMemoryInfo { - private readonly long _highMemoryLoadThresholdBytes; - private readonly long _totalAvailableMemoryBytes; - private readonly long _memoryLoadBytes; - private readonly long _heapSizeBytes; - private readonly long _fragmentedBytes; - private readonly long _totalCommittedBytes; - private readonly long _promotedBytes; - private readonly long _pinnedObjectsCount; - private readonly long _finalizationPendingCount; - private readonly long _index; - private readonly int _generation; - private readonly int _pauseTimePercentage; - private readonly byte _compacted; - private readonly byte _concurrent; - private readonly GCGenerationInfo _generationInfo0; - private readonly GCGenerationInfo _generationInfo1; - private readonly GCGenerationInfo _generationInfo2; - private readonly GCGenerationInfo _generationInfo3; - private readonly GCGenerationInfo _generationInfo4; - private readonly TimeSpan _pauseDuration0; - private readonly TimeSpan _pauseDuration1; + private readonly GCMemoryInfoData _data; internal GCMemoryInfo(GCMemoryInfoData data) { - _highMemoryLoadThresholdBytes = data._highMemoryLoadThresholdBytes; - _totalAvailableMemoryBytes = data._totalAvailableMemoryBytes; - _memoryLoadBytes = data._memoryLoadBytes; - _heapSizeBytes = data._heapSizeBytes; - _fragmentedBytes = data._fragmentedBytes; - _totalCommittedBytes = data._totalCommittedBytes; - _promotedBytes = data._promotedBytes; - _pinnedObjectsCount = data._pinnedObjectsCount; - _finalizationPendingCount = data._finalizationPendingCount; - _index = data._index; - _generation = data._generation; - _pauseTimePercentage = data._pauseTimePercentage; - _compacted = data._compacted; - _concurrent = data._concurrent; - _generationInfo0 = data._generationInfo0; - _generationInfo1 = data._generationInfo1; - _generationInfo2 = data._generationInfo2; - _generationInfo3 = data._generationInfo3; - _generationInfo4 = data._generationInfo4; - _pauseDuration0 = data._pauseDuration0; - _pauseDuration1 = data._pauseDuration1; + _data = data; } /// /// High memory load threshold when this GC occurred /// - public long HighMemoryLoadThresholdBytes => _highMemoryLoadThresholdBytes; + public long HighMemoryLoadThresholdBytes => _data._highMemoryLoadThresholdBytes; /// /// Memory load when this GC occurred /// - public long MemoryLoadBytes => _memoryLoadBytes; + public long MemoryLoadBytes => _data._memoryLoadBytes; /// /// Total available memory for the GC to use when this GC occurred. @@ -157,12 +121,12 @@ internal GCMemoryInfo(GCMemoryInfoData data) /// If the program is run in a container, this will be an implementation-defined fraction of the container's size. /// Else, this is the physical memory on the machine that was available for the GC to use when this GC occurred. /// - public long TotalAvailableMemoryBytes => _totalAvailableMemoryBytes; + public long TotalAvailableMemoryBytes => _data._totalAvailableMemoryBytes; /// /// The total heap size when this GC occurred /// - public long HeapSizeBytes => _heapSizeBytes; + public long HeapSizeBytes => _data._heapSizeBytes; /// /// The total fragmentation when this GC occurred @@ -176,64 +140,64 @@ internal GCMemoryInfo(GCMemoryInfoData data) /// The memory between OBJ_A and OBJ_D marked `F` is considered part of the FragmentedBytes, and will be used to allocate new objects. The memory after OBJ_D will not be /// considered part of the FragmentedBytes, and will also be used to allocate new objects /// - public long FragmentedBytes => _fragmentedBytes; + public long FragmentedBytes => _data._fragmentedBytes; /// /// The index of this GC. GC indices start with 1 and get increased at the beginning of a GC. /// Since the info is updated at the end of a GC, this means you can get the info for a BGC /// with a smaller index than a foreground GC finished earlier. /// - public long Index => _index; + public long Index => _data._index; /// /// The generation this GC collected. Collecting a generation means all its younger generation(s) /// are also collected. /// - public int Generation => _generation; + public int Generation => _data._generation; /// /// Is this a compacting GC or not. /// - public bool Compacted => _compacted != 0; + public bool Compacted => _data._compacted != 0; /// /// Is this a concurrent GC (BGC) or not. /// - public bool Concurrent => _concurrent != 0; + public bool Concurrent => _data._concurrent != 0; /// /// Total committed bytes of the managed heap. /// - public long TotalCommittedBytes => _totalCommittedBytes; + public long TotalCommittedBytes => _data._totalCommittedBytes; /// /// Promoted bytes for this GC. /// - public long PromotedBytes => _promotedBytes; + public long PromotedBytes => _data._promotedBytes; /// /// Number of pinned objects this GC observed. /// - public long PinnedObjectsCount => _pinnedObjectsCount; + public long PinnedObjectsCount => _data._pinnedObjectsCount; /// /// Number of objects ready for finalization this GC observed. /// - public long FinalizationPendingCount => _finalizationPendingCount; + public long FinalizationPendingCount => _data._finalizationPendingCount; /// /// Pause durations. For blocking GCs there's only 1 pause; for BGC there are 2. /// - public ReadOnlySpan PauseDurations => MemoryMarshal.CreateReadOnlySpan(in _pauseDuration0, 2); + public ReadOnlySpan PauseDurations => _data.PauseDurationsAsSpan; /// /// This is the % pause time in GC so far. If it's 1.2%, this number is 1.2. /// - public double PauseTimePercentage => (double)_pauseTimePercentage / 100.0; + public double PauseTimePercentage => (double)_data._pauseTimePercentage / 100.0; /// /// Generation info for all generations. /// - public ReadOnlySpan GenerationInfo => MemoryMarshal.CreateReadOnlySpan(in _generationInfo0, 5); + public ReadOnlySpan GenerationInfo => _data.GenerationInfoAsSpan; } } diff --git a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs index 21eba8223fd0f9..ce69362d8a397d 100644 --- a/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/GC.Mono.cs @@ -248,7 +248,7 @@ private static extern void _GetGCMemoryInfo(out long highMemoryLoadThresholdByte public static GCMemoryInfo GetGCMemoryInfo() { - var data = new GCMemoryInfoData(); + GCMemoryInfoData data = default; _GetGCMemoryInfo(out data._highMemoryLoadThresholdBytes, out data._memoryLoadBytes,