Make GC.GetGCMemoryInfo public (dotnet/coreclr#23779)
authorLudovic Henry <luhenry@microsoft.com>
Wed, 10 Apr 2019 22:57:00 +0000 (15:57 -0700)
committerGitHub <noreply@github.com>
Wed, 10 Apr 2019 22:57:00 +0000 (15:57 -0700)
* Make GC.GetGCMemoryInfo public

This is to be used to allow users to optimize memory consumption based
on what's available to the GC and on the system.

This is based on https://github.com/dotnet/corefx/issues/34631

* Address reviews

- Pass GCMemoryInfo struct to native in place of many arguments
- Make GCMemoryInfo.HeapSize and GCMemoryInfo.Fragmentation long in place of IntPtr

* Address reviews

- Mark GCMemoryInfo readonly

* Rearrange fields and match managed/native names

* Fix compilation

* Use getter shorthand

* Address API Design Review

https://github.com/dotnet/corefx/issues/34631#issuecomment-481358549

* Fix comments

* Fix comments

* Do everything in managed

* Address review

 - Remove unecessary [StructLayout.Sequential]
 - Remove "_" prefix for parameters variables

Commit migrated from https://github.com/dotnet/coreclr/commit/a8d440c432bacf81b770f65042ed1721f38be5e5

src/coreclr/src/System.Private.CoreLib/src/System/GC.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs

index 4485afe..dd043e5 100644 (file)
@@ -50,6 +50,56 @@ namespace System
         NotApplicable = 4
     }
 
+    public readonly struct GCMemoryInfo
+    {
+        /// <summary>
+        /// High memory load threshold when the last GC occured
+        /// </summary>
+        public long HighMemoryLoadThresholdBytes { get; }
+
+        /// <summary>
+        /// Memory load when the last GC ocurred
+        /// </summary>
+        public long MemoryLoadBytes { get; }
+
+        /// <summary>
+        /// Total available memory for the GC to use when the last GC ocurred. By default this is the physical memory on the machine, but it may be customized by specifying a HardLimit.
+        /// </summary>
+        public long TotalAvailableMemoryBytes { get; }
+
+        /// <summary>
+        /// The total heap size when the last GC ocurred
+        /// </summary>
+        public long HeapSizeBytes { get; }
+
+        /// <summary>
+        /// The total fragmentation when the last GC ocurred
+        ///
+        /// Let's take the example below:
+        ///  | OBJ_A |     OBJ_B     | OBJ_C |   OBJ_D   | OBJ_E |
+        ///
+        /// Let's say OBJ_B, OBJ_C and and OBJ_E are garbage and get collected, but the heap does not get compacted, the resulting heap will look like the following:
+        ///  | OBJ_A |           F           |   OBJ_D   |
+        ///
+        /// 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
+        /// </summary>
+        public long FragmentedBytes { get; }
+
+        internal GCMemoryInfo(long highMemoryLoadThresholdBytes,
+                              long memoryLoadBytes,
+                              long totalAvailableMemoryBytes,
+                              long heapSizeBytes,
+                              long fragmentedBytes)
+        {
+            HighMemoryLoadThresholdBytes = highMemoryLoadThresholdBytes;
+            MemoryLoadBytes = memoryLoadBytes;
+            TotalAvailableMemoryBytes = totalAvailableMemoryBytes;
+            HeapSizeBytes = heapSizeBytes;
+            FragmentedBytes = fragmentedBytes;
+        }
+    }
+
     public static class GC
     {
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
@@ -60,6 +110,21 @@ namespace System
                                                   out UIntPtr lastRecordedHeapSize,
                                                   out UIntPtr lastRecordedFragmentation);
 
+        public static GCMemoryInfo GetGCMemoryInfo()
+        {
+            GetMemoryInfo(out uint highMemLoadThreshold,
+                          out ulong totalPhysicalMem,
+                          out uint lastRecordedMemLoad,
+                          out UIntPtr lastRecordedHeapSize,
+                          out UIntPtr lastRecordedFragmentation);
+
+            return new GCMemoryInfo((long)((double)highMemLoadThreshold / 100 * totalPhysicalMem),
+                                    (long)((double)lastRecordedMemLoad / 100 * totalPhysicalMem),
+                                    (long)totalPhysicalMem,
+                                    (long)(ulong)lastRecordedHeapSize,
+                                    (long)(ulong)lastRecordedFragmentation);
+        }
+
         [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
         internal static extern int _StartNoGCRegion(long totalSize, bool lohSizeKnown, long lohSize, bool disallowFullBlockingGC);
 
index 5c5fd99..59e109a 100644 (file)
@@ -301,12 +301,12 @@ namespace System.Buffers
             const double HighPressureThreshold = .90;       // Percent of GC memory pressure threshold we consider "high"
             const double MediumPressureThreshold = .70;     // Percent of GC memory pressure threshold we consider "medium"
 
-            GC.GetMemoryInfo(out uint threshold, out _, out uint lastLoad, out _, out _);
-            if (lastLoad >= threshold * HighPressureThreshold)
+            GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo();
+            if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * HighPressureThreshold)
             {
                 return MemoryPressure.High;
             }
-            else if (lastLoad >= threshold * MediumPressureThreshold)
+            else if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * MediumPressureThreshold)
             {
                 return MemoryPressure.Medium;
             }