Better GC heuristics and ergonomics control. (#3162)
This commit is contained in:
@@ -40,8 +40,6 @@
|
|||||||
#define TRACE_GC 0
|
#define TRACE_GC 0
|
||||||
// Collect memory manager events statistics.
|
// Collect memory manager events statistics.
|
||||||
#define COLLECT_STATISTIC 0
|
#define COLLECT_STATISTIC 0
|
||||||
// Auto-adjust GC thresholds.
|
|
||||||
#define GC_ERGONOMICS 1
|
|
||||||
|
|
||||||
#if COLLECT_STATISTIC
|
#if COLLECT_STATISTIC
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -75,18 +73,18 @@ static_assert(sizeof(ContainerHeader) % kObjectAlignment == 0, "sizeof(Container
|
|||||||
// Collection threshold default (collect after having so many elements in the
|
// Collection threshold default (collect after having so many elements in the
|
||||||
// release candidates set).
|
// release candidates set).
|
||||||
constexpr size_t kGcThreshold = 8 * 1024;
|
constexpr size_t kGcThreshold = 8 * 1024;
|
||||||
#if GC_ERGONOMICS
|
|
||||||
// Ergonomic thresholds.
|
// Ergonomic thresholds.
|
||||||
// If GC to computations time ratio is above that value,
|
// If GC to computations time ratio is above that value,
|
||||||
// increase GC threshold by 1.5 times.
|
// increase GC threshold by 1.5 times.
|
||||||
constexpr double kGcToComputeRatioThreshold = 0.5;
|
constexpr double kGcToComputeRatioThreshold = 0.5;
|
||||||
// Never exceed this value when increasing GC threshold.
|
// Never exceed this value when increasing GC threshold.
|
||||||
constexpr size_t kMaxErgonomicThreshold = 16 * 1024;
|
constexpr size_t kMaxErgonomicThreshold = 16 * 1024;
|
||||||
#endif // GC_ERGONOMICS
|
|
||||||
// Threshold of size for toFree set, triggering actual cycle collector.
|
// Threshold of size for toFree set, triggering actual cycle collector.
|
||||||
constexpr size_t kMaxToFreeSize = 8 * 1024;
|
constexpr size_t kMaxToFreeSize = 8 * 1024;
|
||||||
// How many elements in finalizer queue allowed before cleaning it up.
|
// How many elements in finalizer queue allowed before cleaning it up.
|
||||||
constexpr size_t kFinalizerQueueThreshold = 32;
|
constexpr size_t kFinalizerQueueThreshold = 32;
|
||||||
|
// If allocated that much memory since last GC - force new GC.
|
||||||
|
constexpr size_t kMaxGcAllocThreshold = 8 * 1024 * 1024;
|
||||||
#endif // USE_GC
|
#endif // USE_GC
|
||||||
|
|
||||||
typedef KStdUnorderedSet<ContainerHeader*> ContainerHeaderSet;
|
typedef KStdUnorderedSet<ContainerHeader*> ContainerHeaderSet;
|
||||||
@@ -323,10 +321,11 @@ struct MemoryState {
|
|||||||
// Objects to be released.
|
// Objects to be released.
|
||||||
ContainerHeaderList* toRelease;
|
ContainerHeaderList* toRelease;
|
||||||
|
|
||||||
#if GC_ERGONOMICS
|
bool gcErgonomics;
|
||||||
uint64_t lastGcTimestamp;
|
uint64_t lastGcTimestamp;
|
||||||
#endif
|
|
||||||
|
|
||||||
|
uint64_t allocSinceLastGc;
|
||||||
|
uint64_t allocSinceLastGcThreshold;
|
||||||
#endif // USE_GC
|
#endif // USE_GC
|
||||||
|
|
||||||
#if COLLECT_STATISTIC
|
#if COLLECT_STATISTIC
|
||||||
@@ -688,6 +687,9 @@ ContainerHeader* allocContainer(MemoryState* state, size_t size) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (result == nullptr) {
|
if (result == nullptr) {
|
||||||
|
#if USE_GC
|
||||||
|
state->allocSinceLastGc += size;
|
||||||
|
#endif
|
||||||
result = konanConstructSizedInstance<ContainerHeader>(alignUp(size, kObjectAlignment));
|
result = konanConstructSizedInstance<ContainerHeader>(alignUp(size, kObjectAlignment));
|
||||||
atomicAdd(&allocCount, 1);
|
atomicAdd(&allocCount, 1);
|
||||||
}
|
}
|
||||||
@@ -1015,14 +1017,12 @@ inline void initGcThreshold(MemoryState* state, uint32_t gcThreshold) {
|
|||||||
state->toRelease->reserve(gcThreshold);
|
state->toRelease->reserve(gcThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if GC_ERGONOMICS
|
|
||||||
inline void increaseGcThreshold(MemoryState* state) {
|
inline void increaseGcThreshold(MemoryState* state) {
|
||||||
auto newThreshold = state->gcThreshold * 3 / 2 + 1;
|
auto newThreshold = state->gcThreshold * 3 / 2 + 1;
|
||||||
if (newThreshold <= kMaxErgonomicThreshold) {
|
if (newThreshold <= kMaxErgonomicThreshold) {
|
||||||
initGcThreshold(state, newThreshold);
|
initGcThreshold(state, newThreshold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // GC_ERGONOMICS
|
|
||||||
|
|
||||||
#endif // USE_GC
|
#endif // USE_GC
|
||||||
|
|
||||||
@@ -1402,18 +1402,20 @@ void decrementStack(MemoryState* state) {
|
|||||||
void garbageCollect(MemoryState* state, bool force) {
|
void garbageCollect(MemoryState* state, bool force) {
|
||||||
RuntimeAssert(!state->gcInProgress, "Recursive GC is disallowed");
|
RuntimeAssert(!state->gcInProgress, "Recursive GC is disallowed");
|
||||||
|
|
||||||
|
uint64_t allocSinceLastGc = state->allocSinceLastGc;
|
||||||
|
state->allocSinceLastGc = 0;
|
||||||
|
|
||||||
if (!IsStrictMemoryModel) {
|
if (!IsStrictMemoryModel) {
|
||||||
// In relaxed model we just process finalizer queue and be done with it.
|
// In relaxed model we just process finalizer queue and be done with it.
|
||||||
processFinalizerQueue(state);
|
processFinalizerQueue(state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GC_LOG(">>> %s GC: threshold = %d toFree %d toRelease %d\n", \
|
GC_LOG(">>> %s GC: threshold = %d toFree %d toRelease %d alloc = %lld\n", \
|
||||||
force ? "forced" : "regular", state->gcThreshold, state->toFree->size(), state->toRelease->size())
|
force ? "forced" : "regular", state->gcThreshold, state->toFree->size(),
|
||||||
|
state->toRelease->size(), allocSinceLastGc)
|
||||||
|
|
||||||
#if GC_ERGONOMICS
|
|
||||||
auto gcStartTime = konan::getTimeMicros();
|
auto gcStartTime = konan::getTimeMicros();
|
||||||
#endif
|
|
||||||
|
|
||||||
state->gcInProgress = true;
|
state->gcInProgress = true;
|
||||||
|
|
||||||
@@ -1423,13 +1425,9 @@ void garbageCollect(MemoryState* state, bool force) {
|
|||||||
decrementStack(state);
|
decrementStack(state);
|
||||||
size_t afterDecrements = state->toRelease->size();
|
size_t afterDecrements = state->toRelease->size();
|
||||||
ssize_t stackReferences = afterDecrements - beforeDecrements;
|
ssize_t stackReferences = afterDecrements - beforeDecrements;
|
||||||
if (stackReferences * 5 > state->gcThreshold) {
|
if (state->gcErgonomics && stackReferences * 5 > state->gcThreshold) {
|
||||||
#if GC_ERGONOMICS
|
increaseGcThreshold(state);
|
||||||
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 \n", state->gcThreshold);
|
|
||||||
#else
|
|
||||||
GC_LOG("Too many stack references for the threshold: %d vs %d\n", stackReferences, state->gcThreshold)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GC_LOG("||| GC: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size())
|
GC_LOG("||| GC: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size())
|
||||||
@@ -1444,18 +1442,16 @@ void garbageCollect(MemoryState* state, bool force) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state->gcInProgress = false;
|
state->gcInProgress = false;
|
||||||
|
|
||||||
#if GC_ERGONOMICS
|
|
||||||
auto gcEndTime = konan::getTimeMicros();
|
auto gcEndTime = konan::getTimeMicros();
|
||||||
auto gcToComputeRatio = double(gcEndTime - gcStartTime) / (gcStartTime - state->lastGcTimestamp + 1);
|
if (state->gcErgonomics) {
|
||||||
if (gcToComputeRatio > kGcToComputeRatioThreshold) {
|
auto gcToComputeRatio = double(gcEndTime - gcStartTime) / (gcStartTime - state->lastGcTimestamp + 1);
|
||||||
increaseGcThreshold(state);
|
if (gcToComputeRatio > kGcToComputeRatioThreshold) {
|
||||||
GC_LOG("Adjusting GC threshold to %d\n", state->gcThreshold);
|
increaseGcThreshold(state);
|
||||||
|
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: duration=%lld sinceLast=%lld\n", (gcEndTime - gcStartTime), gcStartTime - state->lastGcTimestamp);
|
||||||
state->lastGcTimestamp = gcEndTime;
|
state->lastGcTimestamp = gcEndTime;
|
||||||
#endif
|
|
||||||
|
|
||||||
GC_LOG("<<< GC: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size())
|
GC_LOG("<<< GC: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1503,6 +1499,8 @@ MemoryState* initMemory() {
|
|||||||
memoryState->gcSuspendCount = 0;
|
memoryState->gcSuspendCount = 0;
|
||||||
memoryState->toRelease = konanConstructInstance<ContainerHeaderList>();
|
memoryState->toRelease = konanConstructInstance<ContainerHeaderList>();
|
||||||
initGcThreshold(memoryState, kGcThreshold);
|
initGcThreshold(memoryState, kGcThreshold);
|
||||||
|
memoryState->allocSinceLastGcThreshold = kMaxGcAllocThreshold;
|
||||||
|
memoryState->gcErgonomics = true;
|
||||||
#endif
|
#endif
|
||||||
atomicAdd(&aliveMemoryStatesCount, 1);
|
atomicAdd(&aliveMemoryStatesCount, 1);
|
||||||
return memoryState;
|
return memoryState;
|
||||||
@@ -1662,10 +1660,22 @@ void updateHeapRefIfNull(ObjHeader** location, const ObjHeader* object) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void checkIfGcNeeded(MemoryState* state) {
|
||||||
|
if (state->allocSinceLastGc > state->allocSinceLastGcThreshold) {
|
||||||
|
// To avoid GC trashing check that at least 10ms passed since last GC.
|
||||||
|
if (konan::getTimeMicros() - state->lastGcTimestamp > 10 * 1000) {
|
||||||
|
garbageCollect(state, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <bool Strict>
|
template <bool Strict>
|
||||||
OBJ_GETTER(allocInstance, const TypeInfo* type_info) {
|
OBJ_GETTER(allocInstance, const TypeInfo* type_info) {
|
||||||
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
|
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
|
||||||
auto* state = memoryState;
|
auto* state = memoryState;
|
||||||
|
#if USE_GC
|
||||||
|
checkIfGcNeeded(state);
|
||||||
|
#endif // USE_GC
|
||||||
auto container = ObjectContainer(state, type_info);
|
auto container = ObjectContainer(state, type_info);
|
||||||
#if USE_GC
|
#if USE_GC
|
||||||
if (Strict) {
|
if (Strict) {
|
||||||
@@ -1682,6 +1692,9 @@ OBJ_GETTER(allocArrayInstance, const TypeInfo* type_info, int32_t elements) {
|
|||||||
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
|
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
|
||||||
if (elements < 0) ThrowIllegalArgumentException();
|
if (elements < 0) ThrowIllegalArgumentException();
|
||||||
auto* state = memoryState;
|
auto* state = memoryState;
|
||||||
|
#if USE_GC
|
||||||
|
checkIfGcNeeded(state);
|
||||||
|
#endif // USE_GC
|
||||||
auto container = ArrayContainer(state, type_info, elements);
|
auto container = ArrayContainer(state, type_info, elements);
|
||||||
#if USE_GC
|
#if USE_GC
|
||||||
if (Strict) {
|
if (Strict) {
|
||||||
@@ -1921,17 +1934,39 @@ void startGC() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setGCThreshold(KInt value) {
|
void setGCThreshold(KInt value) {
|
||||||
GC_LOG("Kotlin_native_internal_setThreshold %d\n", value)
|
GC_LOG("setGCThreshold %d\n", value)
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
initGcThreshold(memoryState, value);
|
initGcThreshold(memoryState, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
KInt getGCThreshold() {
|
KInt getGCThreshold() {
|
||||||
GC_LOG("Kotlin_native_internal_getThreshold %d\n")
|
GC_LOG("getGCThreshold\n")
|
||||||
return memoryState->gcThreshold;
|
return memoryState->gcThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setGCThresholdAllocations(KLong value) {
|
||||||
|
GC_LOG("setGCThresholdAllocations %lld\n", value)
|
||||||
|
if (value > 0) {
|
||||||
|
memoryState->allocSinceLastGcThreshold = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KLong getGCThresholdAllocations() {
|
||||||
|
GC_LOG("getGCThresholdAllocation\n")
|
||||||
|
return memoryState->allocSinceLastGcThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTuneGCThreshold(KBoolean value) {
|
||||||
|
GC_LOG("setTuneGCThreshold %d\n", value)
|
||||||
|
memoryState->gcErgonomics = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
KBoolean getTuneGCThreshold() {
|
||||||
|
GC_LOG("getTuneGCThreshold %d\n")
|
||||||
|
return memoryState->gcErgonomics;
|
||||||
|
}
|
||||||
|
|
||||||
KNativePtr createStablePointer(KRef any) {
|
KNativePtr createStablePointer(KRef any) {
|
||||||
if (any == nullptr) return nullptr;
|
if (any == nullptr) return nullptr;
|
||||||
MEMORY_LOG("CreateStablePointer for %p rc=%d\n", any, any->container() ? any->container()->refCount() : 0)
|
MEMORY_LOG("CreateStablePointer for %p rc=%d\n", any, any->container() ? any->container()->refCount() : 0)
|
||||||
@@ -2304,11 +2339,11 @@ void ObjHeader::destroyMetaObject(TypeInfo** location) {
|
|||||||
|
|
||||||
void ObjectContainer::Init(MemoryState* state, const TypeInfo* typeInfo) {
|
void ObjectContainer::Init(MemoryState* state, const TypeInfo* typeInfo) {
|
||||||
RuntimeAssert(typeInfo->instanceSize_ >= 0, "Must be an object");
|
RuntimeAssert(typeInfo->instanceSize_ >= 0, "Must be an object");
|
||||||
uint32_t alloc_size = sizeof(ContainerHeader) + typeInfo->instanceSize_;
|
uint32_t allocSize = sizeof(ContainerHeader) + typeInfo->instanceSize_;
|
||||||
header_ = allocContainer(state, alloc_size);
|
header_ = allocContainer(state, allocSize);
|
||||||
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
|
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
|
||||||
// One object in this container, no need to set.
|
// One object in this container, no need to set.
|
||||||
header_->setContainerSize(alloc_size);
|
header_->setContainerSize(allocSize);
|
||||||
RuntimeAssert(header_->objectCount() == 1, "Must work properly");
|
RuntimeAssert(header_->objectCount() == 1, "Must work properly");
|
||||||
// header->refCount_ is zero initialized by allocContainer().
|
// header->refCount_ is zero initialized by allocContainer().
|
||||||
SetHeader(GetPlace(), typeInfo);
|
SetHeader(GetPlace(), typeInfo);
|
||||||
@@ -2317,12 +2352,12 @@ void ObjectContainer::Init(MemoryState* state, const TypeInfo* typeInfo) {
|
|||||||
|
|
||||||
void ArrayContainer::Init(MemoryState* state, const TypeInfo* typeInfo, uint32_t elements) {
|
void ArrayContainer::Init(MemoryState* state, const TypeInfo* typeInfo, uint32_t elements) {
|
||||||
RuntimeAssert(typeInfo->instanceSize_ < 0, "Must be an array");
|
RuntimeAssert(typeInfo->instanceSize_ < 0, "Must be an array");
|
||||||
uint32_t alloc_size =
|
uint32_t allocSize =
|
||||||
sizeof(ContainerHeader) + arrayObjectSize(typeInfo, elements);
|
sizeof(ContainerHeader) + arrayObjectSize(typeInfo, elements);
|
||||||
header_ = allocContainer(state, alloc_size);
|
header_ = allocContainer(state, allocSize);
|
||||||
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
|
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
|
||||||
// One object in this container, no need to set.
|
// One object in this container, no need to set.
|
||||||
header_->setContainerSize(alloc_size);
|
header_->setContainerSize(allocSize);
|
||||||
RuntimeAssert(header_->objectCount() == 1, "Must work properly");
|
RuntimeAssert(header_->objectCount() == 1, "Must work properly");
|
||||||
// header->refCount_ is zero initialized by allocContainer().
|
// header->refCount_ is zero initialized by allocContainer().
|
||||||
GetPlace()->count_ = elements;
|
GetPlace()->count_ = elements;
|
||||||
@@ -2622,6 +2657,34 @@ KInt Kotlin_native_internal_GC_getThreshold(KRef) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Kotlin_native_internal_GC_setThresholdAllocations(KRef, KLong value) {
|
||||||
|
#if USE_GC
|
||||||
|
setGCThresholdAllocations(value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
KLong Kotlin_native_internal_GC_getThresholdAllocations(KRef) {
|
||||||
|
#if USE_GC
|
||||||
|
return getGCThresholdAllocations();
|
||||||
|
#else
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kotlin_native_internal_GC_setTuneThreshold(KRef, KInt value) {
|
||||||
|
#if USE_GC
|
||||||
|
setTuneGCThreshold(value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
KBoolean Kotlin_native_internal_GC_getTuneThreshold(KRef) {
|
||||||
|
#if USE_GC
|
||||||
|
return getTuneGCThreshold();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
KNativePtr CreateStablePointer(KRef any) {
|
KNativePtr CreateStablePointer(KRef any) {
|
||||||
return createStablePointer(any);
|
return createStablePointer(any);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,36 @@ object GC {
|
|||||||
get() = getThreshold()
|
get() = getThreshold()
|
||||||
set(value) = setThreshold(value)
|
set(value) = setThreshold(value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GC allocation threshold, controlling how many bytes allocated since last
|
||||||
|
* collection will trigger new GC.
|
||||||
|
*/
|
||||||
|
var thresholdAllocations: Long
|
||||||
|
get() = getThresholdAllocations()
|
||||||
|
set(value) = setThresholdAllocations(value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If GC shall auto-tune thresholds, depending on how much time is spent in collection.
|
||||||
|
*/
|
||||||
|
var autotune: Boolean
|
||||||
|
get() = getTuneThreshold()
|
||||||
|
set(value) = setTuneThreshold(value)
|
||||||
|
|
||||||
@SymbolName("Kotlin_native_internal_GC_getThreshold")
|
@SymbolName("Kotlin_native_internal_GC_getThreshold")
|
||||||
private external fun getThreshold(): Int
|
private external fun getThreshold(): Int
|
||||||
|
|
||||||
@SymbolName("Kotlin_native_internal_GC_setThreshold")
|
@SymbolName("Kotlin_native_internal_GC_setThreshold")
|
||||||
private external fun setThreshold(value: Int)
|
private external fun setThreshold(value: Int)
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_native_internal_GC_getThresholdAllocations")
|
||||||
|
private external fun getThresholdAllocations(): Long
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_native_internal_GC_setThresholdAllocations")
|
||||||
|
private external fun setThresholdAllocations(value: Long)
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_native_internal_GC_getTuneThreshold")
|
||||||
|
private external fun getTuneThreshold(): Boolean
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_native_internal_GC_setTuneThreshold")
|
||||||
|
private external fun setTuneThreshold(value: Boolean)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user