Better GC heuristics and ergonomics control. (#3162)

This commit is contained in:
Nikolay Igotti
2019-07-11 19:23:41 +03:00
committed by GitHub
parent e7f39082c6
commit e2430c3240
2 changed files with 125 additions and 35 deletions
+98 -35
View File
@@ -40,8 +40,6 @@
#define TRACE_GC 0
// Collect memory manager events statistics.
#define COLLECT_STATISTIC 0
// Auto-adjust GC thresholds.
#define GC_ERGONOMICS 1
#if COLLECT_STATISTIC
#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
// release candidates set).
constexpr size_t kGcThreshold = 8 * 1024;
#if GC_ERGONOMICS
// Ergonomic thresholds.
// If GC to computations time ratio is above that value,
// increase GC threshold by 1.5 times.
constexpr double kGcToComputeRatioThreshold = 0.5;
// Never exceed this value when increasing GC threshold.
constexpr size_t kMaxErgonomicThreshold = 16 * 1024;
#endif // GC_ERGONOMICS
// Threshold of size for toFree set, triggering actual cycle collector.
constexpr size_t kMaxToFreeSize = 8 * 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;
#endif // USE_GC
typedef KStdUnorderedSet<ContainerHeader*> ContainerHeaderSet;
@@ -323,10 +321,11 @@ struct MemoryState {
// Objects to be released.
ContainerHeaderList* toRelease;
#if GC_ERGONOMICS
bool gcErgonomics;
uint64_t lastGcTimestamp;
#endif
uint64_t allocSinceLastGc;
uint64_t allocSinceLastGcThreshold;
#endif // USE_GC
#if COLLECT_STATISTIC
@@ -688,6 +687,9 @@ ContainerHeader* allocContainer(MemoryState* state, size_t size) {
}
#endif
if (result == nullptr) {
#if USE_GC
state->allocSinceLastGc += size;
#endif
result = konanConstructSizedInstance<ContainerHeader>(alignUp(size, kObjectAlignment));
atomicAdd(&allocCount, 1);
}
@@ -1015,14 +1017,12 @@ inline void initGcThreshold(MemoryState* state, uint32_t gcThreshold) {
state->toRelease->reserve(gcThreshold);
}
#if GC_ERGONOMICS
inline void increaseGcThreshold(MemoryState* state) {
auto newThreshold = state->gcThreshold * 3 / 2 + 1;
if (newThreshold <= kMaxErgonomicThreshold) {
initGcThreshold(state, newThreshold);
}
}
#endif // GC_ERGONOMICS
#endif // USE_GC
@@ -1402,18 +1402,20 @@ void decrementStack(MemoryState* state) {
void garbageCollect(MemoryState* state, bool force) {
RuntimeAssert(!state->gcInProgress, "Recursive GC is disallowed");
uint64_t allocSinceLastGc = state->allocSinceLastGc;
state->allocSinceLastGc = 0;
if (!IsStrictMemoryModel) {
// In relaxed model we just process finalizer queue and be done with it.
processFinalizerQueue(state);
return;
}
GC_LOG(">>> %s GC: threshold = %d toFree %d toRelease %d\n", \
force ? "forced" : "regular", state->gcThreshold, state->toFree->size(), state->toRelease->size())
GC_LOG(">>> %s GC: threshold = %d toFree %d toRelease %d alloc = %lld\n", \
force ? "forced" : "regular", state->gcThreshold, state->toFree->size(),
state->toRelease->size(), allocSinceLastGc)
#if GC_ERGONOMICS
auto gcStartTime = konan::getTimeMicros();
#endif
state->gcInProgress = true;
@@ -1423,13 +1425,9 @@ void garbageCollect(MemoryState* state, bool force) {
decrementStack(state);
size_t afterDecrements = state->toRelease->size();
ssize_t stackReferences = afterDecrements - beforeDecrements;
if (stackReferences * 5 > state->gcThreshold) {
#if GC_ERGONOMICS
increaseGcThreshold(state);
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
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: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size())
@@ -1444,18 +1442,16 @@ void garbageCollect(MemoryState* state, bool force) {
}
state->gcInProgress = false;
#if GC_ERGONOMICS
auto gcEndTime = konan::getTimeMicros();
auto gcToComputeRatio = double(gcEndTime - gcStartTime) / (gcStartTime - state->lastGcTimestamp + 1);
if (gcToComputeRatio > kGcToComputeRatioThreshold) {
increaseGcThreshold(state);
GC_LOG("Adjusting GC threshold to %d\n", state->gcThreshold);
if (state->gcErgonomics) {
auto gcToComputeRatio = double(gcEndTime - gcStartTime) / (gcStartTime - state->lastGcTimestamp + 1);
if (gcToComputeRatio > kGcToComputeRatioThreshold) {
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);
state->lastGcTimestamp = gcEndTime;
#endif
GC_LOG("<<< GC: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size())
}
@@ -1503,6 +1499,8 @@ MemoryState* initMemory() {
memoryState->gcSuspendCount = 0;
memoryState->toRelease = konanConstructInstance<ContainerHeaderList>();
initGcThreshold(memoryState, kGcThreshold);
memoryState->allocSinceLastGcThreshold = kMaxGcAllocThreshold;
memoryState->gcErgonomics = true;
#endif
atomicAdd(&aliveMemoryStatesCount, 1);
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>
OBJ_GETTER(allocInstance, const TypeInfo* type_info) {
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
auto* state = memoryState;
#if USE_GC
checkIfGcNeeded(state);
#endif // USE_GC
auto container = ObjectContainer(state, type_info);
#if USE_GC
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");
if (elements < 0) ThrowIllegalArgumentException();
auto* state = memoryState;
#if USE_GC
checkIfGcNeeded(state);
#endif // USE_GC
auto container = ArrayContainer(state, type_info, elements);
#if USE_GC
if (Strict) {
@@ -1921,17 +1934,39 @@ void startGC() {
}
void setGCThreshold(KInt value) {
GC_LOG("Kotlin_native_internal_setThreshold %d\n", value)
GC_LOG("setGCThreshold %d\n", value)
if (value > 0) {
initGcThreshold(memoryState, value);
}
}
KInt getGCThreshold() {
GC_LOG("Kotlin_native_internal_getThreshold %d\n")
GC_LOG("getGCThreshold\n")
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) {
if (any == nullptr) return nullptr;
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) {
RuntimeAssert(typeInfo->instanceSize_ >= 0, "Must be an object");
uint32_t alloc_size = sizeof(ContainerHeader) + typeInfo->instanceSize_;
header_ = allocContainer(state, alloc_size);
uint32_t allocSize = sizeof(ContainerHeader) + typeInfo->instanceSize_;
header_ = allocContainer(state, allocSize);
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
// One object in this container, no need to set.
header_->setContainerSize(alloc_size);
header_->setContainerSize(allocSize);
RuntimeAssert(header_->objectCount() == 1, "Must work properly");
// header->refCount_ is zero initialized by allocContainer().
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) {
RuntimeAssert(typeInfo->instanceSize_ < 0, "Must be an array");
uint32_t alloc_size =
uint32_t allocSize =
sizeof(ContainerHeader) + arrayObjectSize(typeInfo, elements);
header_ = allocContainer(state, alloc_size);
header_ = allocContainer(state, allocSize);
RuntimeCheck(header_ != nullptr, "Cannot alloc memory");
// One object in this container, no need to set.
header_->setContainerSize(alloc_size);
header_->setContainerSize(allocSize);
RuntimeAssert(header_->objectCount() == 1, "Must work properly");
// header->refCount_ is zero initialized by allocContainer().
GetPlace()->count_ = elements;
@@ -2622,6 +2657,34 @@ KInt Kotlin_native_internal_GC_getThreshold(KRef) {
#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) {
return createStablePointer(any);
}
@@ -63,9 +63,36 @@ object GC {
get() = getThreshold()
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")
private external fun getThreshold(): Int
@SymbolName("Kotlin_native_internal_GC_setThreshold")
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)
}