Skip to content
Draft
28 changes: 0 additions & 28 deletions src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,34 +124,6 @@ internal enum GC_ALLOC_FLAGS
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern ulong GetGenerationSize(int gen);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_AddMemoryPressure")]
private static partial void _AddMemoryPressure(ulong bytesAllocated);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_RemoveMemoryPressure")]
private static partial void _RemoveMemoryPressure(ulong bytesAllocated);

public static void AddMemoryPressure(long bytesAllocated)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bytesAllocated);
if (IntPtr.Size == 4)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(bytesAllocated, int.MaxValue);
}

_AddMemoryPressure((ulong)bytesAllocated);
}

public static void RemoveMemoryPressure(long bytesAllocated)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bytesAllocated);
if (IntPtr.Size == 4)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(bytesAllocated, int.MaxValue);
}

_RemoveMemoryPressure((ulong)bytesAllocated);
}

// Returns the generation that obj is currently in.
//
public static int GetGeneration(object obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,140 +513,6 @@ public static int CollectionCount(int generation)
return RuntimeImports.RhGetGcCollectionCount(generation, false);
}

// Support for AddMemoryPressure and RemoveMemoryPressure below.
private const uint PressureCount = 4;
#if TARGET_64BIT
private const uint MinGCMemoryPressureBudget = 4 * 1024 * 1024;
#else
private const uint MinGCMemoryPressureBudget = 3 * 1024 * 1024;
#endif

private const uint MaxGCMemoryPressureRatio = 10;

private static int[] s_gcCounts = new int[] { 0, 0, 0 };

private static long[] s_addPressure = new long[] { 0, 0, 0, 0 };
private static long[] s_removePressure = new long[] { 0, 0, 0, 0 };
private static uint s_iteration;

/// <summary>
/// Resets the pressure accounting after a gen2 GC has occurred.
/// </summary>
private static void CheckCollectionCount()
{
if (s_gcCounts[2] != CollectionCount(2))
{
for (int i = 0; i < 3; i++)
{
s_gcCounts[i] = CollectionCount(i);
}

s_iteration++;

uint p = s_iteration % PressureCount;

s_addPressure[p] = 0;
s_removePressure[p] = 0;
}
}

private static long InterlockedAddMemoryPressure(ref long pAugend, long addend)
{
long oldMemValue;
long newMemValue;

do
{
oldMemValue = pAugend;
newMemValue = oldMemValue + addend;

// check for overflow
if (newMemValue < oldMemValue)
{
newMemValue = long.MaxValue;
}
} while (Interlocked.CompareExchange(ref pAugend, newMemValue, oldMemValue) != oldMemValue);

return newMemValue;
}

/// <summary>
/// New AddMemoryPressure implementation (used by RCW and the CLRServicesImpl class)
/// 1. Less sensitive than the original implementation (start budget 3 MB)
/// 2. Focuses more on newly added memory pressure
/// 3. Budget adjusted by effectiveness of last 3 triggered GC (add / remove ratio, max 10x)
/// 4. Budget maxed with 30% of current managed GC size
/// 5. If Gen2 GC is happening naturally, ignore past pressure
///
/// Here's a brief description of the ideal algorithm for Add/Remove memory pressure:
/// Do a GC when (HeapStart is less than X * MemPressureGrowth) where
/// - HeapStart is GC Heap size after doing the last GC
/// - MemPressureGrowth is the net of Add and Remove since the last GC
/// - X is proportional to our guess of the ummanaged memory death rate per GC interval,
/// and would be calculated based on historic data using standard exponential approximation:
/// Xnew = UMDeath/UMTotal * 0.5 + Xprev
/// </summary>
/// <param name="bytesAllocated"></param>
public static void AddMemoryPressure(long bytesAllocated)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bytesAllocated);
#if !TARGET_64BIT
ArgumentOutOfRangeException.ThrowIfGreaterThan(bytesAllocated, int.MaxValue);
#endif

CheckCollectionCount();
uint p = s_iteration % PressureCount;
long newMemValue = InterlockedAddMemoryPressure(ref s_addPressure[p], bytesAllocated);

Debug.Assert(PressureCount == 4, "GC.AddMemoryPressure contains unrolled loops which depend on the PressureCount");

if (newMemValue >= MinGCMemoryPressureBudget)
{
long add = s_addPressure[0] + s_addPressure[1] + s_addPressure[2] + s_addPressure[3] - s_addPressure[p];
long rem = s_removePressure[0] + s_removePressure[1] + s_removePressure[2] + s_removePressure[3] - s_removePressure[p];

long budget = MinGCMemoryPressureBudget;

if (s_iteration >= PressureCount) // wait until we have enough data points
{
// Adjust according to effectiveness of GC
// Scale budget according to past m_addPressure / m_remPressure ratio
if (add >= rem * MaxGCMemoryPressureRatio)
{
budget = MinGCMemoryPressureBudget * MaxGCMemoryPressureRatio;
}
else if (add > rem)
{
Debug.Assert(rem != 0);

// Avoid overflow by calculating addPressure / remPressure as fixed point (1 = 1024)
budget = (add * 1024 / rem) * budget / 1024;
}
}

// If still over budget, check current managed heap size
if (newMemValue >= budget)
{
long heapOver3 = RuntimeImports.RhGetCurrentObjSize() / 3;

if (budget < heapOver3) //Max
{
budget = heapOver3;
}

if (newMemValue >= budget)
{
// last check - if we would exceed 20% of GC "duty cycle", do not trigger GC at this time
if ((RuntimeImports.RhGetGCNow() - RuntimeImports.RhGetLastGCStartTime(2)) > (RuntimeImports.RhGetLastGCDuration(2) * 5))
{
RuntimeImports.RhCollect(2, InternalGCCollectionMode.NonBlocking);
CheckCollectionCount();
}
}
}
}
}

internal struct GCConfigurationContext
{
internal Dictionary<string, object> Configurations;
Expand Down Expand Up @@ -705,18 +571,6 @@ public static unsafe IReadOnlyDictionary<string, object> GetConfigurationVariabl
return context.Configurations!;
}

public static void RemoveMemoryPressure(long bytesAllocated)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bytesAllocated);
#if !TARGET_64BIT
ArgumentOutOfRangeException.ThrowIfGreaterThan(bytesAllocated, int.MaxValue);
#endif

CheckCollectionCount();
uint p = s_iteration % PressureCount;
InterlockedAddMemoryPressure(ref s_removePressure[p], bytesAllocated);
}

public static long GetTotalMemory(bool forceFullCollection)
{
long size = RuntimeImports.RhGetGcTotalMemory();
Expand Down
Loading
Loading