[GC] Added new threshold for collecting cycles in GC (KT-19076) (#4150)

This commit is contained in:
LepilkinaElena
2020-05-07 16:17:23 +03:00
committed by GitHub
parent e20896f99c
commit cfca81cc9d
2 changed files with 118 additions and 9 deletions
+104 -9
View File
@@ -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 <algorithm>
@@ -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<ContainerHeader*> 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<ContainerHeaderList>();
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);
@@ -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