From cfca81cc9d36956ad90912d1c7cc3d4ff6caf009 Mon Sep 17 00:00:00 2001 From: LepilkinaElena Date: Thu, 7 May 2020 16:17:23 +0300 Subject: [PATCH] [GC] Added new threshold for collecting cycles in GC (KT-19076) (#4150) --- runtime/src/main/cpp/Memory.cpp | 113 ++++++++++++++++-- .../main/kotlin/kotlin/native/internal/GC.kt | 14 +++ 2 files changed, 118 insertions(+), 9 deletions(-) diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 73975a3214e..da581d3b9a2 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -47,6 +47,8 @@ #define TRACE_GC 0 // Collect memory manager events statistics. #define COLLECT_STATISTIC 0 +// Define to 1 to print detailed time statistics for GC events. +#define PROFILE_GC 0 #if COLLECT_STATISTIC #include @@ -87,11 +89,19 @@ constexpr double kGcToComputeRatioThreshold = 0.5; // Never exceed this value when increasing GC threshold. constexpr size_t kMaxErgonomicThreshold = 32 * 1024; // Threshold of size for toFree set, triggering actual cycle collector. -constexpr size_t kMaxToFreeSize = 8 * 1024; +constexpr size_t kMaxToFreeSizeThreshold = 8 * 1024; +// Never exceed this value when increasing size for toFree set, triggering actual cycle collector. +constexpr size_t kMaxErgonomicToFreeSizeThreshold = 8 * 1024 * 1024; // How many elements in finalizer queue allowed before cleaning it up. constexpr size_t kFinalizerQueueThreshold = 32; // If allocated that much memory since last GC - force new GC. constexpr size_t kMaxGcAllocThreshold = 8 * 1024 * 1024; +// If the ratio of GC collection cycles time to program execution time is greater this value, +// increase GC threshold for cycles collection. +constexpr double kGcCollectCyclesLoadRatio = 0.3; +// Minimum time of cycles collection to change thresholds. +constexpr size_t kGcCollectCyclesMinimumDuration = 200; + #endif // USE_GC typedef KStdUnorderedSet ContainerHeaderSet; @@ -437,6 +447,8 @@ struct MemoryState { int gcSuspendCount; // How many candidate elements in toRelease shall trigger collection. size_t gcThreshold; + // How many candidate elements in toFree shall trigger cycle collection. + uint64_t gcCollectCyclesThreshold; // If collection is in progress. bool gcInProgress; // Objects to be released. @@ -446,6 +458,7 @@ struct MemoryState { bool gcErgonomics; uint64_t lastGcTimestamp; + uint64_t lastCyclicGcTimestamp; uint32_t gcEpoque; uint64_t allocSinceLastGc; @@ -1165,6 +1178,11 @@ inline void initGcThreshold(MemoryState* state, uint32_t gcThreshold) { state->toRelease->reserve(gcThreshold); } +inline void initGcCollectCyclesThreshold(MemoryState* state, uint64_t gcCollectCyclesThreshold) { + state->gcCollectCyclesThreshold = gcCollectCyclesThreshold; + state->toFree->reserve(gcCollectCyclesThreshold); +} + inline void increaseGcThreshold(MemoryState* state) { auto newThreshold = state->gcThreshold * 3 / 2 + 1; if (newThreshold <= kMaxErgonomicThreshold) { @@ -1172,6 +1190,13 @@ inline void increaseGcThreshold(MemoryState* state) { } } +inline void increaseGcCollectCyclesThreshold(MemoryState* state) { + auto newThreshold = state->gcCollectCyclesThreshold * 2; + if (newThreshold <= kMaxErgonomicToFreeSizeThreshold) { + initGcCollectCyclesThreshold(state, newThreshold); + } +} + #endif // USE_GC #if TRACE_MEMORY && USE_GC @@ -1604,29 +1629,67 @@ void garbageCollect(MemoryState* state, bool force) { if (g_hasCyclicCollector) cyclicLocalGC(); #endif // USE_CYCLIC_GC +#if PROFILE_GC + auto processDecrementsStartTime = konan::getTimeMicros(); +#endif processDecrements(state); +#if PROFILE_GC + auto processDecrementsDuration = konan::getTimeMicros() - processDecrementsStartTime; + GC_LOG("||| GC: processDecrementsDuration = %lld\n", processDecrementsDuration); + auto decrementStackStartTime = konan::getTimeMicros(); +#endif size_t beforeDecrements = state->toRelease->size(); decrementStack(state); size_t afterDecrements = state->toRelease->size(); +#if PROFILE_GC + auto decrementStackDuration = konan::getTimeMicros() - decrementStackStartTime; + GC_LOG("||| GC: decrementStackDuration = %lld\n", decrementStackDuration); +#endif long stackReferences = afterDecrements - beforeDecrements; if (state->gcErgonomics && stackReferences * 5 > state->gcThreshold) { increaseGcThreshold(state); - GC_LOG("||| GC: too many stack references, increased threshold to \n", state->gcThreshold); + GC_LOG("||| GC: too many stack references, increased threshold to %d\n", state->gcThreshold); } GC_LOG("||| GC: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size()) - +#if PROFILE_GC + auto processFinalizerQueueStartTime = konan::getTimeMicros(); +#endif processFinalizerQueue(state); +#if PROFILE_GC + auto processFinalizerQueueDuration = konan::getTimeMicros() - processFinalizerQueueStartTime; + GC_LOG("||| GC: processFinalizerQueueDuration %lld\n", processFinalizerQueueDuration); +#endif - if (force || state->toFree->size() > kMaxToFreeSize) { + if (force || state->toFree->size() > state->gcCollectCyclesThreshold) { + auto cyclicGcStartTime = konan::getTimeMicros(); while (state->toFree->size() > 0) { collectCycles(state); + #if PROFILE_GC + processFinalizerQueueStartTime = konan::getTimeMicros(); + #endif processFinalizerQueue(state); + #if PROFILE_GC + processFinalizerQueueDuration += konan::getTimeMicros() - processFinalizerQueueStartTime; + GC_LOG("||| GC: processFinalizerQueueDuration = %lld\n", processFinalizerQueueDuration); + #endif } + auto cyclicGcEndTime = konan::getTimeMicros(); + #if PROFILE_GC + GC_LOG("||| GC: collectCyclesDuration = %lld\n", cyclicGcEndTime - cyclicGcStartTime); + #endif + auto cyclicGcDuration = cyclicGcEndTime - cyclicGcStartTime; + if (state->gcErgonomics && cyclicGcDuration > kGcCollectCyclesMinimumDuration && + double(cyclicGcDuration) / (cyclicGcStartTime - state->lastCyclicGcTimestamp + 1) > kGcCollectCyclesLoadRatio) { + increaseGcCollectCyclesThreshold(state); + GC_LOG("Adjusting GC collecting cycles threshold to %lld\n", state->gcCollectCyclesThreshold); + } + state->lastCyclicGcTimestamp = cyclicGcEndTime; } state->gcInProgress = false; auto gcEndTime = konan::getTimeMicros(); + if (state->gcErgonomics) { auto gcToComputeRatio = double(gcEndTime - gcStartTime) / (gcStartTime - state->lastGcTimestamp + 1); if (gcToComputeRatio > kGcToComputeRatioThreshold) { @@ -1634,7 +1697,7 @@ void garbageCollect(MemoryState* state, bool force) { GC_LOG("Adjusting GC threshold to %d\n", state->gcThreshold); } } - GC_LOG("GC: duration=%lld sinceLast=%lld\n", (gcEndTime - gcStartTime), gcStartTime - state->lastGcTimestamp); + GC_LOG("GC: gcToComputeRatio=%f duration=%lld sinceLast=%lld\n", double(gcEndTime - gcStartTime) / (gcStartTime - state->lastGcTimestamp + 1), (gcEndTime - gcStartTime), gcStartTime - state->lastGcTimestamp); state->lastGcTimestamp = gcEndTime; #if TRACE_MEMORY @@ -1734,6 +1797,7 @@ MemoryState* initMemory() { memoryState->gcSuspendCount = 0; memoryState->toRelease = konanConstructInstance(); initGcThreshold(memoryState, kGcThreshold); + initGcCollectCyclesThreshold(memoryState, kMaxToFreeSizeThreshold); memoryState->allocSinceLastGcThreshold = kMaxGcAllocThreshold; memoryState->gcErgonomics = true; #endif @@ -1926,6 +1990,7 @@ inline void checkIfGcNeeded(MemoryState* state) { if (state != nullptr && state->allocSinceLastGc > state->allocSinceLastGcThreshold) { // To avoid GC trashing check that at least 10ms passed since last GC. if (konan::getTimeMicros() - state->lastGcTimestamp > 10 * 1000) { + GC_LOG("Calling GC from checkIfGcNeeded: %d\n", state->toRelease->size()) garbageCollect(state, false); } } @@ -2244,9 +2309,10 @@ void startGC() { void setGCThreshold(KInt value) { GC_LOG("setGCThreshold %d\n", value) - if (value > 0) { - initGcThreshold(memoryState, value); + if (value <= 0) { + ThrowIllegalArgumentException(); } + initGcThreshold(memoryState, value); } KInt getGCThreshold() { @@ -2254,11 +2320,26 @@ KInt getGCThreshold() { return memoryState->gcThreshold; } +void setGCCollectCyclesThreshold(KLong value) { + GC_LOG("setGCCollectCyclesThreshold %d\n", value) + if (value <= 0) { + ThrowIllegalArgumentException(); + } + initGcCollectCyclesThreshold(memoryState, value); +} + +KInt getGCCollectCyclesThreshold() { + GC_LOG("getGCCollectCyclesThreshold\n") + return memoryState->gcCollectCyclesThreshold; +} + void setGCThresholdAllocations(KLong value) { GC_LOG("setGCThresholdAllocations %lld\n", value) - if (value > 0) { - memoryState->allocSinceLastGcThreshold = value; + if (value <= 0) { + ThrowIllegalArgumentException(); } + + memoryState->allocSinceLastGcThreshold = value; } KLong getGCThresholdAllocations() { @@ -2953,6 +3034,20 @@ KInt Kotlin_native_internal_GC_getThreshold(KRef) { #endif } +void Kotlin_native_internal_GC_setCollectCyclesThreshold(KRef, KLong value) { +#if USE_GC + setGCCollectCyclesThreshold(value); +#endif +} + +KLong Kotlin_native_internal_GC_getCollectCyclesThreshold(KRef) { +#if USE_GC + return getGCCollectCyclesThreshold(); +#else + return -1; +#endif +} + void Kotlin_native_internal_GC_setThresholdAllocations(KRef, KLong value) { #if USE_GC setGCThresholdAllocations(value); diff --git a/runtime/src/main/kotlin/kotlin/native/internal/GC.kt b/runtime/src/main/kotlin/kotlin/native/internal/GC.kt index 5f0d0906312..5fc746c39d3 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/GC.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/GC.kt @@ -69,6 +69,14 @@ object GC { get() = getThreshold() set(value) = setThreshold(value) + /** + * GC allocation threshold, controlling how frequenly GC collect cycles, and how much time + * this process takes. Bigger values lead to longer GC pauses, but less GCs. + */ + var collectCyclesThreshold: Long + get() = getCollectCyclesThreshold() + set(value) = setCollectCyclesThreshold(value) + /** * GC allocation threshold, controlling how many bytes allocated since last * collection will trigger new GC. @@ -98,6 +106,12 @@ object GC { @SymbolName("Kotlin_native_internal_GC_setThreshold") private external fun setThreshold(value: Int) + @SymbolName("Kotlin_native_internal_GC_getCollectCyclesThreshold") + private external fun getCollectCyclesThreshold(): Long + + @SymbolName("Kotlin_native_internal_GC_setCollectCyclesThreshold") + private external fun setCollectCyclesThreshold(value: Long) + @SymbolName("Kotlin_native_internal_GC_getThresholdAllocations") private external fun getThresholdAllocations(): Long