[GC] Added new threshold for collecting cycles in GC (KT-19076) (#4150)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user