-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Consolidate GC.AddMemoryPressure/RemoveMemoryPressure implementation between NativeAOT and CoreCLR #122824
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
base: main
Are you sure you want to change the base?
Consolidate GC.AddMemoryPressure/RemoveMemoryPressure implementation between NativeAOT and CoreCLR #122824
Changes from 3 commits
0e2d6ca
27dccd1
bcfedcb
291ac22
7acef92
9b131e9
18a611c
8004924
e94e694
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1138,14 +1138,66 @@ FORCEINLINE UINT64 GCInterface::InterlockedSub(UINT64 *pMinuend, UINT64 subtrahe | |
| return newMemValue; | ||
| } | ||
|
|
||
| extern "C" void QCALLTYPE GCInterface_AddMemoryPressure(UINT64 bytesAllocated) | ||
| // New helper FCalls for managed AddMemoryPressure/RemoveMemoryPressure implementation | ||
| FCIMPL0(INT64, GCInterface::GetCurrentObjSize) | ||
jkotas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| QCALL_CONTRACT; | ||
| FCALL_CONTRACT; | ||
|
|
||
| BEGIN_QCALL; | ||
| GCInterface::AddMemoryPressure(bytesAllocated); | ||
| END_QCALL; | ||
| return (INT64)GCHeapUtilities::GetGCHeap()->GetCurrentObjSize(); | ||
| } | ||
| FCIMPLEND | ||
|
|
||
| FCIMPL0(INT64, GCInterface::GetNow) | ||
| { | ||
| FCALL_CONTRACT; | ||
|
|
||
| return (INT64)GCHeapUtilities::GetGCHeap()->GetNow(); | ||
| } | ||
| FCIMPLEND | ||
|
|
||
| FCIMPL1(INT64, GCInterface::GetLastGCStartTime, INT32 generation) | ||
| { | ||
| FCALL_CONTRACT; | ||
|
|
||
| return (INT64)GCHeapUtilities::GetGCHeap()->GetLastGCStartTime(generation); | ||
| } | ||
| FCIMPLEND | ||
|
|
||
| FCIMPL1(INT64, GCInterface::GetLastGCDuration, INT32 generation) | ||
| { | ||
| FCALL_CONTRACT; | ||
|
|
||
| return (INT64)GCHeapUtilities::GetGCHeap()->GetLastGCDuration(generation); | ||
| } | ||
| FCIMPLEND | ||
|
|
||
| FCIMPL1(VOID, GCInterface::SendEtwAddMemoryPressureEvent, UINT64 bytesAllocated) | ||
| { | ||
| FCALL_CONTRACT; | ||
|
|
||
| FC_INNER_PROLOG(GCInterface::SendEtwAddMemoryPressureEvent); | ||
| FireEtwIncreaseMemoryPressure(bytesAllocated, GetClrInstanceId()); | ||
| FC_INNER_EPILOG(); | ||
| } | ||
| FCIMPLEND | ||
|
|
||
| FCIMPL1(VOID, GCInterface::SendEtwRemoveMemoryPressureEvent, UINT64 bytesAllocated) | ||
| { | ||
| FCALL_CONTRACT; | ||
|
|
||
| FC_INNER_PROLOG(GCInterface::SendEtwRemoveMemoryPressureEvent); | ||
| EX_TRY | ||
| { | ||
| FireEtwDecreaseMemoryPressure(bytesAllocated, GetClrInstanceId()); | ||
| } | ||
| EX_CATCH | ||
| { | ||
| // Ignore failures | ||
| } | ||
| EX_END_CATCH | ||
| FC_INNER_EPILOG(); | ||
| } | ||
| FCIMPLEND | ||
|
|
||
| extern "C" void QCALLTYPE GCInterface_EnumerateConfigurationValues(void* configurationContext, EnumerateConfigurationValuesCallback callback) | ||
| { | ||
|
|
@@ -1252,54 +1304,7 @@ uint64_t GCInterface::GetGenerationBudget(int generation) | |
| return GCHeapUtilities::GetGCHeap()->GetGenerationBudget(generation); | ||
| } | ||
|
|
||
| #ifdef HOST_64BIT | ||
| const unsigned MIN_MEMORYPRESSURE_BUDGET = 4 * 1024 * 1024; // 4 MB | ||
| #else // HOST_64BIT | ||
| const unsigned MIN_MEMORYPRESSURE_BUDGET = 3 * 1024 * 1024; // 3 MB | ||
| #endif // HOST_64BIT | ||
|
|
||
| const unsigned MAX_MEMORYPRESSURE_RATIO = 10; // 40 MB or 30 MB | ||
|
|
||
|
|
||
| // Resets pressure accounting after a gen2 GC has occurred. | ||
| void GCInterface::CheckCollectionCount() | ||
| { | ||
| LIMITED_METHOD_CONTRACT; | ||
|
|
||
| IGCHeap * pHeap = GCHeapUtilities::GetGCHeap(); | ||
|
|
||
| if (m_gc_counts[2] != pHeap->CollectionCount(2)) | ||
| { | ||
| for (int i = 0; i < 3; i++) | ||
| { | ||
| m_gc_counts[i] = pHeap->CollectionCount(i); | ||
| } | ||
|
|
||
| m_iteration++; | ||
|
|
||
| UINT p = m_iteration % MEM_PRESSURE_COUNT; | ||
|
|
||
| m_addPressure[p] = 0; // new pressure will be accumulated here | ||
| m_remPressure[p] = 0; | ||
| } | ||
| } | ||
|
|
||
| // AddMemoryPressure implementation | ||
| // | ||
| // 1. Start budget - MIN_MEMORYPRESSURE_BUDGET | ||
| // 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 < 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 | ||
| // | ||
| // Static wrapper methods to call managed AddMemoryPressure/RemoveMemoryPressure | ||
| void GCInterface::AddMemoryPressure(UINT64 bytesAllocated) | ||
| { | ||
| CONTRACTL | ||
|
|
@@ -1310,78 +1315,8 @@ void GCInterface::AddMemoryPressure(UINT64 bytesAllocated) | |
| } | ||
| CONTRACTL_END; | ||
|
|
||
| CheckCollectionCount(); | ||
|
|
||
| UINT p = m_iteration % MEM_PRESSURE_COUNT; | ||
|
|
||
| UINT64 newMemValue = InterlockedAdd(&m_addPressure[p], bytesAllocated); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete GCInterface::InterlockedAdd. It looks unused.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 8004924. Deleted GCInterface::InterlockedAdd from both comutilnative.h and comutilnative.cpp. |
||
|
|
||
| static_assert(MEM_PRESSURE_COUNT == 4, "AddMemoryPressure contains unrolled loops which depend on MEM_PRESSURE_COUNT"); | ||
|
|
||
| UINT64 add = m_addPressure[0] + m_addPressure[1] + m_addPressure[2] + m_addPressure[3] - m_addPressure[p]; | ||
| UINT64 rem = m_remPressure[0] + m_remPressure[1] + m_remPressure[2] + m_remPressure[3] - m_remPressure[p]; | ||
|
|
||
| STRESS_LOG4(LF_GCINFO, LL_INFO10000, "AMP Add: %llu => added=%llu total_added=%llu total_removed=%llu", | ||
| bytesAllocated, newMemValue, add, rem); | ||
|
|
||
| SendEtwAddMemoryPressureEvent(bytesAllocated); | ||
|
|
||
| if (newMemValue >= MIN_MEMORYPRESSURE_BUDGET) | ||
| { | ||
| UINT64 budget = MIN_MEMORYPRESSURE_BUDGET; | ||
|
|
||
| if (m_iteration >= MEM_PRESSURE_COUNT) // 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 * MAX_MEMORYPRESSURE_RATIO) | ||
| { | ||
| budget = MIN_MEMORYPRESSURE_BUDGET * MAX_MEMORYPRESSURE_RATIO; | ||
| } | ||
| else if (add > rem) | ||
| { | ||
| CONSISTENCY_CHECK(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) | ||
| { | ||
| IGCHeap *pGCHeap = GCHeapUtilities::GetGCHeap(); | ||
| UINT64 heapOver3 = pGCHeap->GetCurrentObjSize() / 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 ((size_t)(pGCHeap->GetNow() - pGCHeap->GetLastGCStartTime(2)) > (pGCHeap->GetLastGCDuration(2) * 5)) | ||
| { | ||
| STRESS_LOG6(LF_GCINFO, LL_INFO10000, "AMP Budget: pressure=%llu ? budget=%llu (total_added=%llu, total_removed=%llu, mng_heap=%llu) pos=%d", | ||
| newMemValue, budget, add, rem, heapOver3 * 3, m_iteration); | ||
|
|
||
| GarbageCollectModeAny(2); | ||
|
|
||
| CheckCollectionCount(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extern "C" void QCALLTYPE GCInterface_RemoveMemoryPressure(UINT64 bytesAllocated) | ||
| { | ||
| QCALL_CONTRACT; | ||
|
|
||
| BEGIN_QCALL; | ||
| GCInterface::RemoveMemoryPressure(bytesAllocated); | ||
| END_QCALL; | ||
| // Call the managed implementation through QCall wrapper | ||
| GCInterface_AddMemoryPressureForExternal(bytesAllocated); | ||
jkotas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| void GCInterface::RemoveMemoryPressure(UINT64 bytesAllocated) | ||
|
|
@@ -1394,66 +1329,54 @@ void GCInterface::RemoveMemoryPressure(UINT64 bytesAllocated) | |
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot Change the contract of this method THROWS GC_TRIGGERS MODE_ANY. Delete unnecessary switch to pre-emptive mode around the call to this method in LoaderAllocator::Terminate. Change Thread finalizer (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in e94e694. Changed GCInterface::RemoveMemoryPressure contract to THROWS GC_TRIGGERS MODE_ANY, removed GCX_COOP() and GCX_PREEMP() switches, and converted ThreadNative::Finalize to QCall (ThreadNative_Finalize) so it can call the managed RemoveMemoryPressure implementation. |
||
| CONTRACTL_END; | ||
|
|
||
| CheckCollectionCount(); | ||
|
|
||
| UINT p = m_iteration % MEM_PRESSURE_COUNT; | ||
|
|
||
| SendEtwRemoveMemoryPressureEvent(bytesAllocated); | ||
|
|
||
| InterlockedAdd(&m_remPressure[p], bytesAllocated); | ||
|
|
||
| STRESS_LOG2(LF_GCINFO, LL_INFO10000, "AMP Remove: %llu => removed=%llu", | ||
| bytesAllocated, m_remPressure[p]); | ||
| // Call the managed implementation through QCall wrapper | ||
| GCInterface_RemoveMemoryPressureForExternal(bytesAllocated); | ||
jkotas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| inline void GCInterface::SendEtwAddMemoryPressureEvent(UINT64 bytesAllocated) | ||
| // QCall wrappers for managed AddMemoryPressure/RemoveMemoryPressure implementation | ||
| extern "C" void QCALLTYPE GCInterface_AddMemoryPressureForExternal(UINT64 bytesAllocated) | ||
| { | ||
| CONTRACTL | ||
| { | ||
| THROWS; | ||
| GC_TRIGGERS; | ||
| MODE_ANY; | ||
| } | ||
| CONTRACTL_END; | ||
| QCALL_CONTRACT; | ||
|
|
||
| FireEtwIncreaseMemoryPressure(bytesAllocated, GetClrInstanceId()); | ||
| } | ||
| BEGIN_QCALL; | ||
|
|
||
| // Out-of-line helper to avoid EH prolog/epilog in functions that otherwise don't throw. | ||
| NOINLINE void GCInterface::SendEtwRemoveMemoryPressureEvent(UINT64 bytesAllocated) | ||
| { | ||
| CONTRACTL | ||
| { | ||
| NOTHROW; | ||
| GC_NOTRIGGER; | ||
| MODE_ANY; | ||
| } | ||
| CONTRACTL_END; | ||
| GCX_COOP(); | ||
|
|
||
| EX_TRY | ||
| { | ||
| FireEtwDecreaseMemoryPressure(bytesAllocated, GetClrInstanceId()); | ||
| } | ||
| EX_CATCH | ||
| { | ||
| // Ignore failures | ||
| } | ||
| EX_END_CATCH | ||
| // Directly invoke the managed GC.AddMemoryPressure method | ||
| MethodTable* pMT = CoreLibBinder::GetClass(CLASS__GC); | ||
| MethodDesc* pMD = CoreLibBinder::GetMethod(METHOD__GC__ADD_MEMORY_PRESSURE); | ||
jkotas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| MethodDescCallSite addMemoryPressure(pMD); | ||
jkotas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ARG_SLOT args[] = { | ||
| (ARG_SLOT)bytesAllocated | ||
| }; | ||
|
|
||
| addMemoryPressure.Call(args); | ||
|
|
||
| END_QCALL; | ||
| } | ||
|
|
||
| // Out-of-line helper to avoid EH prolog/epilog in functions that otherwise don't throw. | ||
| NOINLINE void GCInterface::GarbageCollectModeAny(int generation) | ||
| extern "C" void QCALLTYPE GCInterface_RemoveMemoryPressureForExternal(UINT64 bytesAllocated) | ||
| { | ||
| CONTRACTL | ||
| { | ||
| THROWS; | ||
| GC_TRIGGERS; | ||
| MODE_ANY; | ||
| } | ||
| CONTRACTL_END; | ||
| QCALL_CONTRACT; | ||
|
|
||
| BEGIN_QCALL; | ||
|
|
||
| GCX_COOP(); | ||
| GCHeapUtilities::GetGCHeap()->GarbageCollect(generation, false, collection_non_blocking); | ||
|
|
||
| // Directly invoke the managed GC.RemoveMemoryPressure method | ||
| MethodDesc* pMD = CoreLibBinder::GetMethod(METHOD__GC__REMOVE_MEMORY_PRESSURE); | ||
jkotas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| MethodDescCallSite removeMemoryPressure(pMD); | ||
|
|
||
| ARG_SLOT args[] = { | ||
| (ARG_SLOT)bytesAllocated | ||
| }; | ||
|
|
||
| removeMemoryPressure.Call(args); | ||
|
|
||
| END_QCALL; | ||
| } | ||
|
|
||
| // | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.