[K/N] Move GC schedulers into modules
Additionally, - rename disabled into manual - rename with_timer into adaptive - remove on_safepoints - store schedulers in GlobalData, ThreadData directly
This commit is contained in:
committed by
Space Team
parent
0ad98ff610
commit
63e65a482c
+7
-7
@@ -6,12 +6,12 @@
|
|||||||
package org.jetbrains.kotlin.backend.konan
|
package org.jetbrains.kotlin.backend.konan
|
||||||
|
|
||||||
// Must match `GCSchedulerType` in CompilerConstants.hpp
|
// Must match `GCSchedulerType` in CompilerConstants.hpp
|
||||||
enum class GCSchedulerType(val value: Int, val deprecatedWithReplacement: GCSchedulerType? = null) {
|
enum class GCSchedulerType(val deprecatedWithReplacement: GCSchedulerType? = null) {
|
||||||
MANUAL(0),
|
MANUAL,
|
||||||
ADAPTIVE(1),
|
ADAPTIVE,
|
||||||
AGGRESSIVE(3),
|
AGGRESSIVE,
|
||||||
// Deprecated:
|
// Deprecated:
|
||||||
DISABLED(0, deprecatedWithReplacement = MANUAL),
|
DISABLED(deprecatedWithReplacement = MANUAL),
|
||||||
WITH_TIMER(1, deprecatedWithReplacement = ADAPTIVE),
|
WITH_TIMER(deprecatedWithReplacement = ADAPTIVE),
|
||||||
ON_SAFE_POINTS(2, deprecatedWithReplacement = ADAPTIVE),
|
ON_SAFE_POINTS(deprecatedWithReplacement = ADAPTIVE),
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -272,6 +272,21 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
|||||||
internal val runtimeNativeLibraries: List<String> = mutableListOf<String>().apply {
|
internal val runtimeNativeLibraries: List<String> = mutableListOf<String>().apply {
|
||||||
if (debug) add("debug.bc")
|
if (debug) add("debug.bc")
|
||||||
add("common_gc.bc")
|
add("common_gc.bc")
|
||||||
|
add("common_gcScheduler.bc")
|
||||||
|
when (gcSchedulerType) {
|
||||||
|
GCSchedulerType.MANUAL -> {
|
||||||
|
add("manual_gcScheduler.bc")
|
||||||
|
}
|
||||||
|
GCSchedulerType.ADAPTIVE -> {
|
||||||
|
add("adaptive_gcScheduler.bc")
|
||||||
|
}
|
||||||
|
GCSchedulerType.AGGRESSIVE -> {
|
||||||
|
add("aggressive_gcScheduler.bc")
|
||||||
|
}
|
||||||
|
GCSchedulerType.DISABLED, GCSchedulerType.WITH_TIMER, GCSchedulerType.ON_SAFE_POINTS -> {
|
||||||
|
throw IllegalStateException("Deprecated options must have already been handled")
|
||||||
|
}
|
||||||
|
}
|
||||||
if (allocationMode == AllocationMode.CUSTOM) {
|
if (allocationMode == AllocationMode.CUSTOM) {
|
||||||
add("experimental_memory_manager_custom.bc")
|
add("experimental_memory_manager_custom.bc")
|
||||||
add("concurrent_ms_gc_custom.bc")
|
add("concurrent_ms_gc_custom.bc")
|
||||||
|
|||||||
-1
@@ -3000,7 +3000,6 @@ internal fun NativeGenerationState.generateRuntimeConstantsModule() : LLVMModule
|
|||||||
setRuntimeConstGlobal("Kotlin_runtimeLogs", runtimeLogs)
|
setRuntimeConstGlobal("Kotlin_runtimeLogs", runtimeLogs)
|
||||||
setRuntimeConstGlobal("Kotlin_freezingEnabled", llvm.constInt32(if (config.freezing.enableFreezeAtRuntime) 1 else 0))
|
setRuntimeConstGlobal("Kotlin_freezingEnabled", llvm.constInt32(if (config.freezing.enableFreezeAtRuntime) 1 else 0))
|
||||||
setRuntimeConstGlobal("Kotlin_freezingChecksEnabled", llvm.constInt32(if (config.freezing.enableFreezeChecks) 1 else 0))
|
setRuntimeConstGlobal("Kotlin_freezingChecksEnabled", llvm.constInt32(if (config.freezing.enableFreezeChecks) 1 else 0))
|
||||||
setRuntimeConstGlobal("Kotlin_gcSchedulerType", llvm.constInt32(config.gcSchedulerType.value))
|
|
||||||
|
|
||||||
return llvmModule
|
return llvmModule
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ bitcode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module("custom_alloc") {
|
module("custom_alloc") {
|
||||||
headersDirs.from(files("src/main/cpp", "src/mm/cpp", "src/gc/common/cpp", "src/gc/cms/cpp"))
|
headersDirs.from(files("src/main/cpp", "src/mm/cpp", "src/gc/common/cpp", "src/gcScheduler/common/cpp", "src/gc/cms/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
test {}
|
test {}
|
||||||
@@ -253,7 +253,7 @@ bitcode {
|
|||||||
|
|
||||||
module("experimental_memory_manager") {
|
module("experimental_memory_manager") {
|
||||||
srcRoot.set(layout.projectDirectory.dir("src/mm"))
|
srcRoot.set(layout.projectDirectory.dir("src/mm"))
|
||||||
headersDirs.from(files("src/gc/common/cpp", "src/main/cpp"))
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/main/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
testFixtures {}
|
testFixtures {}
|
||||||
@@ -265,7 +265,7 @@ bitcode {
|
|||||||
|
|
||||||
module("experimental_memory_manager_custom") {
|
module("experimental_memory_manager_custom") {
|
||||||
srcRoot.set(layout.projectDirectory.dir("src/mm"))
|
srcRoot.set(layout.projectDirectory.dir("src/mm"))
|
||||||
headersDirs.from(files("src/gc/common/cpp", "src/main/cpp", "src/custom_alloc/cpp"))
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/main/cpp", "src/custom_alloc/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
testFixtures {}
|
testFixtures {}
|
||||||
@@ -279,7 +279,7 @@ bitcode {
|
|||||||
|
|
||||||
module("common_gc") {
|
module("common_gc") {
|
||||||
srcRoot.set(layout.projectDirectory.dir("src/gc/common"))
|
srcRoot.set(layout.projectDirectory.dir("src/gc/common"))
|
||||||
headersDirs.from(files("src/mm/cpp", "src/main/cpp"))
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
test {}
|
test {}
|
||||||
@@ -290,7 +290,7 @@ bitcode {
|
|||||||
|
|
||||||
module("noop_gc") {
|
module("noop_gc") {
|
||||||
srcRoot.set(layout.projectDirectory.dir("src/gc/noop"))
|
srcRoot.set(layout.projectDirectory.dir("src/gc/noop"))
|
||||||
headersDirs.from(files("src/gc/noop/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
testFixtures {}
|
testFixtures {}
|
||||||
@@ -301,7 +301,7 @@ bitcode {
|
|||||||
|
|
||||||
module("same_thread_ms_gc") {
|
module("same_thread_ms_gc") {
|
||||||
srcRoot.set(layout.projectDirectory.dir("src/gc/stms"))
|
srcRoot.set(layout.projectDirectory.dir("src/gc/stms"))
|
||||||
headersDirs.from(files("src/gc/stms/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
testFixtures {}
|
testFixtures {}
|
||||||
@@ -313,7 +313,7 @@ bitcode {
|
|||||||
|
|
||||||
module("concurrent_ms_gc") {
|
module("concurrent_ms_gc") {
|
||||||
srcRoot.set(layout.projectDirectory.dir("src/gc/cms"))
|
srcRoot.set(layout.projectDirectory.dir("src/gc/cms"))
|
||||||
headersDirs.from(files("src/gc/cms/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
testFixtures {}
|
testFixtures {}
|
||||||
@@ -325,7 +325,7 @@ bitcode {
|
|||||||
|
|
||||||
module("concurrent_ms_gc_custom") {
|
module("concurrent_ms_gc_custom") {
|
||||||
srcRoot.set(layout.projectDirectory.dir("src/gc/cms"))
|
srcRoot.set(layout.projectDirectory.dir("src/gc/cms"))
|
||||||
headersDirs.from(files("src/gc/cms/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp", "src/custom_alloc/cpp"))
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp", "src/custom_alloc/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
testFixtures {}
|
testFixtures {}
|
||||||
@@ -337,33 +337,87 @@ bitcode {
|
|||||||
onlyIf { target.supportsThreads() }
|
onlyIf { target.supportsThreads() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module("common_gcScheduler") {
|
||||||
|
srcRoot.set(layout.projectDirectory.dir("src/gcScheduler/common"))
|
||||||
|
headersDirs.from(files("src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
|
sourceSets {
|
||||||
|
main {}
|
||||||
|
testFixtures {}
|
||||||
|
test {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onlyIf { target.supportsThreads() }
|
||||||
|
}
|
||||||
|
|
||||||
|
module("manual_gcScheduler") {
|
||||||
|
srcRoot.set(layout.projectDirectory.dir("src/gcScheduler/manual"))
|
||||||
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
|
sourceSets {
|
||||||
|
main {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onlyIf { target.supportsThreads() }
|
||||||
|
}
|
||||||
|
|
||||||
|
module("adaptive_gcScheduler") {
|
||||||
|
srcRoot.set(layout.projectDirectory.dir("src/gcScheduler/adaptive"))
|
||||||
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
|
sourceSets {
|
||||||
|
main {}
|
||||||
|
test {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onlyIf { target.supportsThreads() }
|
||||||
|
}
|
||||||
|
|
||||||
|
module("aggressive_gcScheduler") {
|
||||||
|
srcRoot.set(layout.projectDirectory.dir("src/gcScheduler/aggressive"))
|
||||||
|
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
|
sourceSets {
|
||||||
|
main {}
|
||||||
|
test {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onlyIf { target.supportsThreads() }
|
||||||
|
}
|
||||||
|
|
||||||
testsGroup("custom_alloc_runtime_tests") {
|
testsGroup("custom_alloc_runtime_tests") {
|
||||||
testedModules.addAll("custom_alloc")
|
testedModules.addAll("custom_alloc")
|
||||||
testSupportModules.addAll("main", "experimental_memory_manager", "common_gc", "concurrent_ms_gc", "objc")
|
testSupportModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "manual_gcScheduler", "concurrent_ms_gc", "objc")
|
||||||
}
|
}
|
||||||
|
|
||||||
testsGroup("experimentalMM_mimalloc_runtime_tests") {
|
testsGroup("experimentalMM_mimalloc_runtime_tests") {
|
||||||
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "same_thread_ms_gc", "mimalloc", "opt_alloc", "objc")
|
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "manual_gcScheduler", "same_thread_ms_gc", "mimalloc", "opt_alloc", "objc")
|
||||||
}
|
}
|
||||||
|
|
||||||
testsGroup("experimentalMM_std_alloc_runtime_tests") {
|
testsGroup("experimentalMM_std_alloc_runtime_tests") {
|
||||||
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "same_thread_ms_gc", "std_alloc", "objc")
|
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "manual_gcScheduler", "same_thread_ms_gc", "std_alloc", "objc")
|
||||||
}
|
}
|
||||||
|
|
||||||
testsGroup("experimentalMM_cms_mimalloc_runtime_tests") {
|
testsGroup("experimentalMM_cms_mimalloc_runtime_tests") {
|
||||||
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "concurrent_ms_gc", "mimalloc", "opt_alloc", "objc")
|
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "manual_gcScheduler", "concurrent_ms_gc", "mimalloc", "opt_alloc", "objc")
|
||||||
}
|
}
|
||||||
|
|
||||||
testsGroup("experimentalMM_cms_std_alloc_runtime_tests") {
|
testsGroup("experimentalMM_cms_std_alloc_runtime_tests") {
|
||||||
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "concurrent_ms_gc", "std_alloc", "objc")
|
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "manual_gcScheduler", "concurrent_ms_gc", "std_alloc", "objc")
|
||||||
}
|
}
|
||||||
|
|
||||||
testsGroup("experimentalMM_noop_mimalloc_runtime_tests") {
|
testsGroup("experimentalMM_noop_mimalloc_runtime_tests") {
|
||||||
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "noop_gc", "mimalloc", "opt_alloc", "objc")
|
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "manual_gcScheduler", "noop_gc", "mimalloc", "opt_alloc", "objc")
|
||||||
}
|
}
|
||||||
|
|
||||||
testsGroup("experimentalMM_noop_std_alloc_runtime_tests") {
|
testsGroup("experimentalMM_noop_std_alloc_runtime_tests") {
|
||||||
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "noop_gc", "std_alloc", "objc")
|
testedModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "manual_gcScheduler", "noop_gc", "std_alloc", "objc")
|
||||||
|
}
|
||||||
|
|
||||||
|
testsGroup("aggressive_gcScheduler_runtime_tests") {
|
||||||
|
testedModules.addAll("aggressive_gcScheduler")
|
||||||
|
testSupportModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "noop_gc", "std_alloc", "objc")
|
||||||
|
}
|
||||||
|
|
||||||
|
testsGroup("adaptive_gcScheduler_runtime_tests") {
|
||||||
|
testedModules.addAll("adaptive_gcScheduler")
|
||||||
|
testSupportModules.addAll("main", "experimental_memory_manager", "common_gc", "common_gcScheduler", "noop_gc", "std_alloc", "objc")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ uint64_t ArrayAllocatedDataSize(const TypeInfo* typeInfo, uint32_t count) noexce
|
|||||||
return AlignUp<uint64_t>(sizeof(HeapArrayHeader) + membersSize, kObjectAlignment);
|
return AlignUp<uint64_t>(sizeof(HeapArrayHeader) + membersSize, kObjectAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomAllocator::CustomAllocator(Heap& heap, gc::GCSchedulerThreadData& gcScheduler) noexcept :
|
CustomAllocator::CustomAllocator(Heap& heap, gcScheduler::GCSchedulerThreadData& gcScheduler) noexcept :
|
||||||
heap_(heap), gcScheduler_(gcScheduler), nextFitPage_(nullptr), extraObjectPage_(nullptr) {
|
heap_(heap), gcScheduler_(gcScheduler), nextFitPage_(nullptr), extraObjectPage_(nullptr) {
|
||||||
CustomAllocInfo("CustomAllocator::CustomAllocator(heap)");
|
CustomAllocInfo("CustomAllocator::CustomAllocator(heap)");
|
||||||
memset(fixedBlockPages_, 0, sizeof(fixedBlockPages_));
|
memset(fixedBlockPages_, 0, sizeof(fixedBlockPages_));
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace kotlin::alloc {
|
|||||||
|
|
||||||
class CustomAllocator {
|
class CustomAllocator {
|
||||||
public:
|
public:
|
||||||
explicit CustomAllocator(Heap& heap, gc::GCSchedulerThreadData& gcScheduler) noexcept;
|
explicit CustomAllocator(Heap& heap, gcScheduler::GCSchedulerThreadData& gcScheduler) noexcept;
|
||||||
|
|
||||||
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ private:
|
|||||||
uint8_t* AllocateInFixedBlockPage(uint32_t cellCount) noexcept;
|
uint8_t* AllocateInFixedBlockPage(uint32_t cellCount) noexcept;
|
||||||
|
|
||||||
Heap& heap_;
|
Heap& heap_;
|
||||||
gc::GCSchedulerThreadData& gcScheduler_;
|
gcScheduler::GCSchedulerThreadData& gcScheduler_;
|
||||||
NextFitPage* nextFitPage_;
|
NextFitPage* nextFitPage_;
|
||||||
FixedBlockPage* fixedBlockPages_[FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 1];
|
FixedBlockPage* fixedBlockPages_[FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 1];
|
||||||
ExtraObjectPage* extraObjectPage_;
|
ExtraObjectPage* extraObjectPage_;
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ TEST(CustomAllocTest, SmallAllocNonNull) {
|
|||||||
fakeTypes[i] = {.typeInfo_ = &fakeTypes[i], .instanceSize_ = 8 * i, .flags_ = 0};
|
fakeTypes[i] = {.typeInfo_ = &fakeTypes[i], .instanceSize_ = 8 * i, .flags_ = 0};
|
||||||
}
|
}
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gc::GCSchedulerConfig config;
|
kotlin::gcScheduler::GCSchedulerConfig config;
|
||||||
kotlin::gc::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
||||||
CustomAllocator ca(heap, schedulerData);
|
CustomAllocator ca(heap, schedulerData);
|
||||||
ObjHeader* obj[N];
|
ObjHeader* obj[N];
|
||||||
for (int i = 1; i < N; ++i) {
|
for (int i = 1; i < N; ++i) {
|
||||||
@@ -43,8 +43,8 @@ TEST(CustomAllocTest, SmallAllocSameFixedBlockPage) {
|
|||||||
const int N = FIXED_BLOCK_PAGE_CELL_COUNT / FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE;
|
const int N = FIXED_BLOCK_PAGE_CELL_COUNT / FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE;
|
||||||
for (int blocks = MIN_BLOCK_SIZE; blocks < FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE; ++blocks) {
|
for (int blocks = MIN_BLOCK_SIZE; blocks < FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE; ++blocks) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gc::GCSchedulerConfig config;
|
kotlin::gcScheduler::GCSchedulerConfig config;
|
||||||
kotlin::gc::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
||||||
CustomAllocator ca(heap, schedulerData);
|
CustomAllocator ca(heap, schedulerData);
|
||||||
TypeInfo fakeType = {.typeInfo_ = &fakeType, .instanceSize_ = 8 * blocks, .flags_ = 0};
|
TypeInfo fakeType = {.typeInfo_ = &fakeType, .instanceSize_ = 8 * blocks, .flags_ = 0};
|
||||||
uint8_t* first = reinterpret_cast<uint8_t*>(ca.CreateObject(&fakeType));
|
uint8_t* first = reinterpret_cast<uint8_t*>(ca.CreateObject(&fakeType));
|
||||||
@@ -58,8 +58,8 @@ TEST(CustomAllocTest, SmallAllocSameFixedBlockPage) {
|
|||||||
|
|
||||||
TEST(CustomAllocTest, FixedBlockPageThreshold) {
|
TEST(CustomAllocTest, FixedBlockPageThreshold) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gc::GCSchedulerConfig config;
|
kotlin::gcScheduler::GCSchedulerConfig config;
|
||||||
kotlin::gc::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
||||||
CustomAllocator ca(heap, schedulerData);
|
CustomAllocator ca(heap, schedulerData);
|
||||||
const int FROM = FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE - 10;
|
const int FROM = FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE - 10;
|
||||||
const int TO = FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 10;
|
const int TO = FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 10;
|
||||||
@@ -71,8 +71,8 @@ TEST(CustomAllocTest, FixedBlockPageThreshold) {
|
|||||||
|
|
||||||
TEST(CustomAllocTest, NextFitPageThreshold) {
|
TEST(CustomAllocTest, NextFitPageThreshold) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gc::GCSchedulerConfig config;
|
kotlin::gcScheduler::GCSchedulerConfig config;
|
||||||
kotlin::gc::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
||||||
CustomAllocator ca(heap, schedulerData);
|
CustomAllocator ca(heap, schedulerData);
|
||||||
const int FROM = NEXT_FIT_PAGE_MAX_BLOCK_SIZE - 10;
|
const int FROM = NEXT_FIT_PAGE_MAX_BLOCK_SIZE - 10;
|
||||||
const int TO = NEXT_FIT_PAGE_MAX_BLOCK_SIZE + 10;
|
const int TO = NEXT_FIT_PAGE_MAX_BLOCK_SIZE + 10;
|
||||||
@@ -85,9 +85,9 @@ TEST(CustomAllocTest, NextFitPageThreshold) {
|
|||||||
TEST(CustomAllocTest, TwoAllocatorsDifferentPages) {
|
TEST(CustomAllocTest, TwoAllocatorsDifferentPages) {
|
||||||
for (int blocks = MIN_BLOCK_SIZE; blocks < 2000; ++blocks) {
|
for (int blocks = MIN_BLOCK_SIZE; blocks < 2000; ++blocks) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gc::GCSchedulerConfig config;
|
kotlin::gcScheduler::GCSchedulerConfig config;
|
||||||
kotlin::gc::GCSchedulerThreadData schedulerData1(config, [](auto&) {});
|
kotlin::gcScheduler::GCSchedulerThreadData schedulerData1(config, [](auto&) {});
|
||||||
kotlin::gc::GCSchedulerThreadData schedulerData2(config, [](auto&) {});
|
kotlin::gcScheduler::GCSchedulerThreadData schedulerData2(config, [](auto&) {});
|
||||||
CustomAllocator ca1(heap, schedulerData1);
|
CustomAllocator ca1(heap, schedulerData1);
|
||||||
CustomAllocator ca2(heap, schedulerData2);
|
CustomAllocator ca2(heap, schedulerData2);
|
||||||
TypeInfo fakeType = {.typeInfo_ = &fakeType, .instanceSize_ = 8 * blocks, .flags_ = 0};
|
TypeInfo fakeType = {.typeInfo_ = &fakeType, .instanceSize_ = 8 * blocks, .flags_ = 0};
|
||||||
@@ -102,8 +102,8 @@ using Data = typename kotlin::mm::ExtraObjectData;
|
|||||||
|
|
||||||
TEST(CustomAllocTest, AllocExtraObjectNonNullZeroed) {
|
TEST(CustomAllocTest, AllocExtraObjectNonNullZeroed) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gc::GCSchedulerConfig config;
|
kotlin::gcScheduler::GCSchedulerConfig config;
|
||||||
kotlin::gc::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
||||||
CustomAllocator ca(heap, schedulerData);
|
CustomAllocator ca(heap, schedulerData);
|
||||||
for (int i = 1; i < 10; ++i) {
|
for (int i = 1; i < 10; ++i) {
|
||||||
uint8_t* obj = reinterpret_cast<uint8_t*>(ca.CreateExtraObject());
|
uint8_t* obj = reinterpret_cast<uint8_t*>(ca.CreateExtraObject());
|
||||||
|
|||||||
@@ -109,21 +109,16 @@ void gc::ConcurrentMarkAndSweep::ThreadData::OnSuspendForGC() noexcept {
|
|||||||
|
|
||||||
#ifndef CUSTOM_ALLOCATOR
|
#ifndef CUSTOM_ALLOCATOR
|
||||||
gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(
|
gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(
|
||||||
mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory, GCScheduler& gcScheduler) noexcept :
|
mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory, gcScheduler::GCScheduler& gcScheduler) noexcept :
|
||||||
objectFactory_(objectFactory),
|
objectFactory_(objectFactory),
|
||||||
#else
|
#else
|
||||||
gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(GCScheduler& gcScheduler) noexcept :
|
gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(gcScheduler::GCScheduler& gcScheduler) noexcept :
|
||||||
#endif
|
#endif
|
||||||
gcScheduler_(gcScheduler),
|
gcScheduler_(gcScheduler),
|
||||||
finalizerProcessor_([this](int64_t epoch) {
|
finalizerProcessor_([this](int64_t epoch) {
|
||||||
GCHandle::getByEpoch(epoch).finalizersDone();
|
GCHandle::getByEpoch(epoch).finalizersDone();
|
||||||
state_.finalized(epoch);
|
state_.finalized(epoch);
|
||||||
}) {
|
}) {
|
||||||
gcScheduler_.SetScheduleGC([this]() NO_INLINE {
|
|
||||||
// This call acquires a lock, so we need to ensure that we're in the safe state.
|
|
||||||
NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true);
|
|
||||||
state_.schedule();
|
|
||||||
});
|
|
||||||
gcThread_ = ScopedThread(ScopedThread::attributes().name("GC thread"), [this] {
|
gcThread_ = ScopedThread(ScopedThread::attributes().name("GC thread"), [this] {
|
||||||
while (true) {
|
while (true) {
|
||||||
auto epoch = state_.waitScheduled();
|
auto epoch = state_.waitScheduled();
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ public:
|
|||||||
|
|
||||||
using Allocator = AllocatorWithGC<Allocator, ThreadData>;
|
using Allocator = AllocatorWithGC<Allocator, ThreadData>;
|
||||||
|
|
||||||
explicit ThreadData(ConcurrentMarkAndSweep& gc, mm::ThreadData& threadData, GCSchedulerThreadData& gcScheduler) noexcept :
|
explicit ThreadData(
|
||||||
|
ConcurrentMarkAndSweep& gc, mm::ThreadData& threadData, gcScheduler::GCSchedulerThreadData& gcScheduler) noexcept :
|
||||||
gc_(gc), threadData_(threadData), gcScheduler_(gcScheduler) {}
|
gc_(gc), threadData_(threadData), gcScheduler_(gcScheduler) {}
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ public:
|
|||||||
friend ConcurrentMarkAndSweep;
|
friend ConcurrentMarkAndSweep;
|
||||||
ConcurrentMarkAndSweep& gc_;
|
ConcurrentMarkAndSweep& gc_;
|
||||||
mm::ThreadData& threadData_;
|
mm::ThreadData& threadData_;
|
||||||
GCSchedulerThreadData& gcScheduler_;
|
gcScheduler::GCSchedulerThreadData& gcScheduler_;
|
||||||
std::atomic<bool> marking_;
|
std::atomic<bool> marking_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,9 +112,9 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CUSTOM_ALLOCATOR
|
#ifdef CUSTOM_ALLOCATOR
|
||||||
explicit ConcurrentMarkAndSweep(GCScheduler& scheduler) noexcept;
|
explicit ConcurrentMarkAndSweep(gcScheduler::GCScheduler& scheduler) noexcept;
|
||||||
#else
|
#else
|
||||||
ConcurrentMarkAndSweep(mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory, GCScheduler& scheduler) noexcept;
|
ConcurrentMarkAndSweep(mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory, gcScheduler::GCScheduler& scheduler) noexcept;
|
||||||
#endif
|
#endif
|
||||||
~ConcurrentMarkAndSweep();
|
~ConcurrentMarkAndSweep();
|
||||||
|
|
||||||
@@ -129,6 +130,8 @@ public:
|
|||||||
alloc::Heap& heap() noexcept { return heap_; }
|
alloc::Heap& heap() noexcept { return heap_; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void Schedule() noexcept { state_.schedule(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PerformFullGC(int64_t epoch) noexcept;
|
void PerformFullGC(int64_t epoch) noexcept;
|
||||||
|
|
||||||
@@ -137,7 +140,7 @@ private:
|
|||||||
#else
|
#else
|
||||||
alloc::Heap heap_;
|
alloc::Heap heap_;
|
||||||
#endif
|
#endif
|
||||||
GCScheduler& gcScheduler_;
|
gcScheduler::GCScheduler& gcScheduler_;
|
||||||
|
|
||||||
GCStateHolder state_;
|
GCStateHolder state_;
|
||||||
ScopedThread gcThread_;
|
ScopedThread gcThread_;
|
||||||
|
|||||||
@@ -13,25 +13,17 @@
|
|||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
namespace {
|
gc::GC::ThreadData::ThreadData(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
||||||
|
impl_(std_support::make_unique<Impl>(gc, gcScheduler, threadData)) {}
|
||||||
ALWAYS_INLINE void SafePointRegular(gc::GC::ThreadData& threadData, size_t weight) noexcept {
|
|
||||||
threadData.impl().gcScheduler().OnSafePointRegular(weight);
|
|
||||||
mm::SuspendIfRequested();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : impl_(std_support::make_unique<Impl>(gc, threadData)) {}
|
|
||||||
|
|
||||||
gc::GC::ThreadData::~ThreadData() = default;
|
gc::GC::ThreadData::~ThreadData() = default;
|
||||||
|
|
||||||
ALWAYS_INLINE void gc::GC::ThreadData::SafePointFunctionPrologue() noexcept {
|
ALWAYS_INLINE void gc::GC::ThreadData::SafePointFunctionPrologue() noexcept {
|
||||||
SafePointRegular(*this, GCSchedulerThreadData::kFunctionPrologueWeight);
|
mm::SuspendIfRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE void gc::GC::ThreadData::SafePointLoopBody() noexcept {
|
ALWAYS_INLINE void gc::GC::ThreadData::SafePointLoopBody() noexcept {
|
||||||
SafePointRegular(*this, GCSchedulerThreadData::kLoopBodyWeight);
|
mm::SuspendIfRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::ThreadData::Schedule() noexcept {
|
void gc::GC::ThreadData::Schedule() noexcept {
|
||||||
@@ -74,15 +66,11 @@ ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeI
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::ThreadData::OnStoppedForGC() noexcept {
|
|
||||||
impl_->gcScheduler().OnStoppedForGC();
|
|
||||||
}
|
|
||||||
|
|
||||||
void gc::GC::ThreadData::OnSuspendForGC() noexcept {
|
void gc::GC::ThreadData::OnSuspendForGC() noexcept {
|
||||||
impl_->gc().OnSuspendForGC();
|
impl_->gc().OnSuspendForGC();
|
||||||
}
|
}
|
||||||
|
|
||||||
gc::GC::GC() noexcept : impl_(std_support::make_unique<Impl>()) {}
|
gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {}
|
||||||
|
|
||||||
gc::GC::~GC() = default;
|
gc::GC::~GC() = default;
|
||||||
|
|
||||||
@@ -99,10 +87,6 @@ size_t gc::GC::GetTotalHeapObjectsSizeBytes() const noexcept {
|
|||||||
return allocatedBytes();
|
return allocatedBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
|
||||||
return impl_->gcScheduler().config();
|
|
||||||
}
|
|
||||||
|
|
||||||
void gc::GC::ClearForTests() noexcept {
|
void gc::GC::ClearForTests() noexcept {
|
||||||
impl_->gc().StopFinalizerThreadIfRunning();
|
impl_->gc().StopFinalizerThreadIfRunning();
|
||||||
#ifndef CUSTOM_ALLOCATOR
|
#ifndef CUSTOM_ALLOCATOR
|
||||||
@@ -137,3 +121,7 @@ ALWAYS_INLINE void gc::GC::processArrayInMark(void* state, ArrayHeader* array) n
|
|||||||
ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {
|
ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {
|
||||||
gc::internal::processFieldInMark<gc::internal::MarkTraits>(state, field);
|
gc::internal::processFieldInMark<gc::internal::MarkTraits>(state, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gc::GC::Schedule() noexcept {
|
||||||
|
impl_->gc().Schedule();
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,37 +21,34 @@ using GCImpl = ConcurrentMarkAndSweep;
|
|||||||
class GC::Impl : private Pinned {
|
class GC::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
#ifdef CUSTOM_ALLOCATOR
|
#ifdef CUSTOM_ALLOCATOR
|
||||||
Impl() noexcept : gc_(gcScheduler_) {}
|
explicit Impl(gcScheduler::GCScheduler& gcScheduler) noexcept : gc_(gcScheduler) {}
|
||||||
#else
|
#else
|
||||||
Impl() noexcept : gc_(objectFactory_, gcScheduler_) {}
|
explicit Impl(gcScheduler::GCScheduler& gcScheduler) noexcept : gc_(objectFactory_, gcScheduler) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef CUSTOM_ALLOCATOR
|
#ifndef CUSTOM_ALLOCATOR
|
||||||
mm::ObjectFactory<gc::GCImpl>& objectFactory() noexcept { return objectFactory_; }
|
mm::ObjectFactory<gc::GCImpl>& objectFactory() noexcept { return objectFactory_; }
|
||||||
#endif
|
#endif
|
||||||
GCScheduler& gcScheduler() noexcept { return gcScheduler_; }
|
|
||||||
GCImpl& gc() noexcept { return gc_; }
|
GCImpl& gc() noexcept { return gc_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifndef CUSTOM_ALLOCATOR
|
#ifndef CUSTOM_ALLOCATOR
|
||||||
mm::ObjectFactory<gc::GCImpl> objectFactory_;
|
mm::ObjectFactory<gc::GCImpl> objectFactory_;
|
||||||
#endif
|
#endif
|
||||||
GCScheduler gcScheduler_;
|
|
||||||
GCImpl gc_;
|
GCImpl gc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GC::ThreadData::Impl : private Pinned {
|
class GC::ThreadData::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
Impl(GC& gc, mm::ThreadData& threadData) noexcept :
|
Impl(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
||||||
gcScheduler_(gc.impl_->gcScheduler().NewThreadData()),
|
gc_(gc.impl_->gc(), threadData, gcScheduler),
|
||||||
gc_(gc.impl_->gc(), threadData, gcScheduler_),
|
|
||||||
#ifndef CUSTOM_ALLOCATOR
|
#ifndef CUSTOM_ALLOCATOR
|
||||||
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()) {}
|
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()) {}
|
||||||
#else
|
#else
|
||||||
alloc_(gc.impl_->gc().heap(), gcScheduler_) {}
|
alloc_(gc.impl_->gc().heap(), gcScheduler) {
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
GCSchedulerThreadData& gcScheduler() noexcept { return gcScheduler_; }
|
|
||||||
GCImpl::ThreadData& gc() noexcept { return gc_; }
|
GCImpl::ThreadData& gc() noexcept { return gc_; }
|
||||||
#ifdef CUSTOM_ALLOCATOR
|
#ifdef CUSTOM_ALLOCATOR
|
||||||
alloc::CustomAllocator& alloc() noexcept { return alloc_; }
|
alloc::CustomAllocator& alloc() noexcept { return alloc_; }
|
||||||
@@ -60,7 +57,6 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GCSchedulerThreadData gcScheduler_;
|
|
||||||
GCImpl::ThreadData gc_;
|
GCImpl::ThreadData gc_;
|
||||||
#ifdef CUSTOM_ALLOCATOR
|
#ifdef CUSTOM_ALLOCATOR
|
||||||
alloc::CustomAllocator alloc_;
|
alloc::CustomAllocator alloc_;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
class Impl;
|
class Impl;
|
||||||
|
|
||||||
ThreadData(GC& gc, mm::ThreadData& threadData) noexcept;
|
ThreadData(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept;
|
||||||
~ThreadData();
|
~ThreadData();
|
||||||
|
|
||||||
Impl& impl() noexcept { return *impl_; }
|
Impl& impl() noexcept { return *impl_; }
|
||||||
@@ -45,14 +45,13 @@ public:
|
|||||||
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
||||||
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept;
|
ArrayHeader* CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept;
|
||||||
|
|
||||||
void OnStoppedForGC() noexcept;
|
|
||||||
void OnSuspendForGC() noexcept;
|
void OnSuspendForGC() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std_support::unique_ptr<Impl> impl_;
|
std_support::unique_ptr<Impl> impl_;
|
||||||
};
|
};
|
||||||
|
|
||||||
GC() noexcept;
|
explicit GC(gcScheduler::GCScheduler& gcScheduler) noexcept;
|
||||||
~GC();
|
~GC();
|
||||||
|
|
||||||
Impl& impl() noexcept { return *impl_; }
|
Impl& impl() noexcept { return *impl_; }
|
||||||
@@ -61,8 +60,6 @@ public:
|
|||||||
|
|
||||||
size_t GetTotalHeapObjectsSizeBytes() const noexcept;
|
size_t GetTotalHeapObjectsSizeBytes() const noexcept;
|
||||||
|
|
||||||
gc::GCSchedulerConfig& gcSchedulerConfig() noexcept;
|
|
||||||
|
|
||||||
void ClearForTests() noexcept;
|
void ClearForTests() noexcept;
|
||||||
|
|
||||||
void StartFinalizerThreadIfNeeded() noexcept;
|
void StartFinalizerThreadIfNeeded() noexcept;
|
||||||
@@ -73,6 +70,9 @@ public:
|
|||||||
static void processArrayInMark(void* state, ArrayHeader* array) noexcept;
|
static void processArrayInMark(void* state, ArrayHeader* array) noexcept;
|
||||||
static void processFieldInMark(void* state, ObjHeader* field) noexcept;
|
static void processFieldInMark(void* state, ObjHeader* field) noexcept;
|
||||||
|
|
||||||
|
// TODO: This should be moved into the scheduler.
|
||||||
|
void Schedule() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std_support::unique_ptr<Impl> impl_;
|
std_support::unique_ptr<Impl> impl_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "GCScheduler.hpp"
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "CompilerConstants.hpp"
|
|
||||||
#include "GCSchedulerImpl.hpp"
|
|
||||||
#include "GlobalData.hpp"
|
|
||||||
#include "KAssert.h"
|
|
||||||
#include "Porting.h"
|
|
||||||
#include "ThreadRegistry.hpp"
|
|
||||||
#include "ThreadData.hpp"
|
|
||||||
|
|
||||||
using namespace kotlin;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std_support::unique_ptr<gc::GCSchedulerData> MakeGCSchedulerData(
|
|
||||||
gc::SchedulerType type, gc::GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept {
|
|
||||||
switch (type) {
|
|
||||||
case gc::SchedulerType::kDisabled:
|
|
||||||
RuntimeLogDebug({kTagGC}, "GC scheduler disabled");
|
|
||||||
return std_support::make_unique<gc::internal::GCEmptySchedulerData>();
|
|
||||||
case gc::SchedulerType::kWithTimer:
|
|
||||||
#ifndef KONAN_NO_THREADS
|
|
||||||
RuntimeLogDebug({kTagGC}, "Initializing timer-based GC scheduler");
|
|
||||||
return std_support::make_unique<gc::internal::GCSchedulerDataWithTimer<steady_clock>>(config, std::move(scheduleGC));
|
|
||||||
#else
|
|
||||||
RuntimeFail("GC scheduler with timer is not supported on this platform");
|
|
||||||
#endif
|
|
||||||
case gc::SchedulerType::kOnSafepoints:
|
|
||||||
RuntimeLogDebug({kTagGC}, "Initializing safe-point-based GC scheduler");
|
|
||||||
return std_support::make_unique<gc::internal::GCSchedulerDataOnSafepoints<steady_clock>>(config, std::move(scheduleGC));
|
|
||||||
case gc::SchedulerType::kAggressive:
|
|
||||||
RuntimeLogDebug({kTagGC}, "Initializing aggressive GC scheduler");
|
|
||||||
return std_support::make_unique<gc::internal::GCSchedulerDataAggressive>(config, std::move(scheduleGC));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void gc::GCScheduler::SetScheduleGC(std::function<void()> scheduleGC) noexcept {
|
|
||||||
RuntimeAssert(static_cast<bool>(scheduleGC), "scheduleGC cannot be empty");
|
|
||||||
RuntimeAssert(!static_cast<bool>(scheduleGC_), "scheduleGC must not have been set");
|
|
||||||
scheduleGC_ = std::move(scheduleGC);
|
|
||||||
RuntimeAssert(gcData_ == nullptr, "gcData_ must not be set prior to scheduleGC call");
|
|
||||||
gcData_ = MakeGCSchedulerData(compiler::getGCSchedulerType(), config_, scheduleGC_);
|
|
||||||
}
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GCScheduler.hpp"
|
|
||||||
|
|
||||||
#include "AppStateTracking.hpp"
|
|
||||||
#include "Clock.hpp"
|
|
||||||
#include "GlobalData.hpp"
|
|
||||||
#include "StackTrace.hpp"
|
|
||||||
#include "std_support/UnorderedSet.hpp"
|
|
||||||
|
|
||||||
#ifndef KONAN_NO_THREADS
|
|
||||||
#include "RepeatedTimer.hpp"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace kotlin::gc::internal {
|
|
||||||
|
|
||||||
class HeapGrowthController {
|
|
||||||
public:
|
|
||||||
explicit HeapGrowthController(gc::GCSchedulerConfig& config) noexcept : config_(config) {}
|
|
||||||
|
|
||||||
// Called by the mutators.
|
|
||||||
void OnAllocated(size_t allocatedBytes) noexcept { allocatedBytes_ += allocatedBytes; }
|
|
||||||
|
|
||||||
// Called by the GC thread.
|
|
||||||
void OnPerformFullGC() noexcept { allocatedBytes_ = 0; }
|
|
||||||
|
|
||||||
// Called by the GC thread.
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept {
|
|
||||||
lastAliveSetBytes_ = bytes;
|
|
||||||
|
|
||||||
if (config_.autoTune.load()) {
|
|
||||||
double targetHeapBytes = static_cast<double>(bytes) / config_.targetHeapUtilization;
|
|
||||||
if (!std::isfinite(targetHeapBytes)) {
|
|
||||||
// This shouldn't happen in practice: targetHeapUtilization is in (0, 1]. But in case it does, don't touch anything.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
double minHeapBytes = static_cast<double>(config_.minHeapBytes.load());
|
|
||||||
double maxHeapBytes = static_cast<double>(config_.maxHeapBytes.load());
|
|
||||||
targetHeapBytes = std::min(std::max(targetHeapBytes, minHeapBytes), maxHeapBytes);
|
|
||||||
config_.targetHeapBytes = static_cast<int64_t>(targetHeapBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by the mutators.
|
|
||||||
bool NeedsGC() const noexcept {
|
|
||||||
uint64_t currentHeapBytes = allocatedBytes_.load() + lastAliveSetBytes_.load();
|
|
||||||
uint64_t targetHeapBytes = config_.targetHeapBytes;
|
|
||||||
return currentHeapBytes >= targetHeapBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
gc::GCSchedulerConfig& config_;
|
|
||||||
// Updated by both the mutators and the GC thread.
|
|
||||||
std::atomic<size_t> allocatedBytes_ = 0;
|
|
||||||
// Updated by the GC thread, read by the mutators.
|
|
||||||
std::atomic<size_t> lastAliveSetBytes_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Clock>
|
|
||||||
class RegularIntervalPacer {
|
|
||||||
public:
|
|
||||||
using TimePoint = std::chrono::time_point<Clock>;
|
|
||||||
|
|
||||||
explicit RegularIntervalPacer(gc::GCSchedulerConfig& config) noexcept : config_(config), lastGC_(Clock::now()) {}
|
|
||||||
|
|
||||||
// Called by the mutators or the timer thread.
|
|
||||||
bool NeedsGC() const noexcept {
|
|
||||||
auto currentTime = Clock::now();
|
|
||||||
return currentTime >= lastGC_.load() + config_.regularGcInterval();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by the GC thread.
|
|
||||||
void OnPerformFullGC() noexcept { lastGC_ = Clock::now(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
gc::GCSchedulerConfig& config_;
|
|
||||||
// Updated by the GC thread, read by the mutators or the timer thread.
|
|
||||||
std::atomic<TimePoint> lastGC_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <size_t SafePointStackSize = 16>
|
|
||||||
class SafePointTracker {
|
|
||||||
public:
|
|
||||||
using SafePointID = kotlin::StackTrace<SafePointStackSize>;
|
|
||||||
|
|
||||||
explicit SafePointTracker(size_t maxSize = 100000) : maxSize_(maxSize) {}
|
|
||||||
|
|
||||||
/** Returns whether the GC must be triggered on the current safe point or not. */
|
|
||||||
NO_INLINE bool registerCurrentSafePoint(size_t skipFrames) noexcept {
|
|
||||||
auto currentSP = SafePointID::current(skipFrames + 1);
|
|
||||||
|
|
||||||
std::unique_lock lock(mutex_);
|
|
||||||
|
|
||||||
// TODO: Consider replacing this naive cleaning with an LRU cache.
|
|
||||||
if (metSafePoints_.size() >= maxSize()) {
|
|
||||||
RuntimeLogDebug({kTagGC}, "Clear safe point tracker set since it exceeded maximal size");
|
|
||||||
metSafePoints_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool inserted = metSafePoints_.insert(currentSP).second;
|
|
||||||
return inserted;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t maxSize() { return maxSize_; }
|
|
||||||
|
|
||||||
size_t size() { return metSafePoints_.size(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
size_t maxSize_;
|
|
||||||
|
|
||||||
// TODO: Consider replacing mutex + global set with thread local sets sychronized on STW.
|
|
||||||
SpinLock<MutexThreadStateHandling::kIgnore> mutex_;
|
|
||||||
std_support::unordered_set<SafePointID> metSafePoints_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GCEmptySchedulerData : public gc::GCSchedulerData {
|
|
||||||
void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override {}
|
|
||||||
void OnPerformFullGC() noexcept override {}
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef KONAN_NO_THREADS
|
|
||||||
|
|
||||||
template <typename Clock>
|
|
||||||
class GCSchedulerDataWithTimer : public gc::GCSchedulerData {
|
|
||||||
public:
|
|
||||||
GCSchedulerDataWithTimer(gc::GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
|
|
||||||
config_(config),
|
|
||||||
appStateTracking_(mm::GlobalData::Instance().appStateTracking()),
|
|
||||||
heapGrowthController_(config),
|
|
||||||
regularIntervalPacer_(config),
|
|
||||||
scheduleGC_(std::move(scheduleGC)),
|
|
||||||
timer_("GC Timer thread", config_.regularGcInterval(), [this] {
|
|
||||||
if (appStateTracking_.state() == mm::AppStateTracking::State::kBackground) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (regularIntervalPacer_.NeedsGC()) {
|
|
||||||
RuntimeLogInfo({kTagGC}, "Scheduling GC by timer");
|
|
||||||
scheduleGC_();
|
|
||||||
}
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override {
|
|
||||||
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
|
|
||||||
if (heapGrowthController_.NeedsGC()) {
|
|
||||||
RuntimeLogInfo({kTagGC}, "Scheduling GC by allocation");
|
|
||||||
scheduleGC_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnPerformFullGC() noexcept override {
|
|
||||||
heapGrowthController_.OnPerformFullGC();
|
|
||||||
regularIntervalPacer_.OnPerformFullGC();
|
|
||||||
timer_.restart(config_.regularGcInterval());
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
gc::GCSchedulerConfig& config_;
|
|
||||||
mm::AppStateTracking& appStateTracking_;
|
|
||||||
HeapGrowthController heapGrowthController_;
|
|
||||||
RegularIntervalPacer<Clock> regularIntervalPacer_;
|
|
||||||
std::function<void()> scheduleGC_;
|
|
||||||
RepeatedTimer<Clock> timer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // !KONAN_NO_THREADS
|
|
||||||
|
|
||||||
template <typename Clock>
|
|
||||||
class GCSchedulerDataOnSafepoints : public gc::GCSchedulerData {
|
|
||||||
public:
|
|
||||||
GCSchedulerDataOnSafepoints(gc::GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
|
|
||||||
heapGrowthController_(config), regularIntervalPacer_(config), scheduleGC_(std::move(scheduleGC)) {}
|
|
||||||
|
|
||||||
void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override {
|
|
||||||
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
|
|
||||||
if (heapGrowthController_.NeedsGC()) {
|
|
||||||
scheduleGC_();
|
|
||||||
} else if (regularIntervalPacer_.NeedsGC()) {
|
|
||||||
scheduleGC_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnPerformFullGC() noexcept override {
|
|
||||||
heapGrowthController_.OnPerformFullGC();
|
|
||||||
regularIntervalPacer_.OnPerformFullGC();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
HeapGrowthController heapGrowthController_;
|
|
||||||
RegularIntervalPacer<Clock> regularIntervalPacer_;
|
|
||||||
std::function<void()> scheduleGC_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GCSchedulerDataAggressive : public gc::GCSchedulerData {
|
|
||||||
public:
|
|
||||||
GCSchedulerDataAggressive(gc::GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
|
|
||||||
scheduleGC_(std::move(scheduleGC)), heapGrowthController_(config) {
|
|
||||||
// Trigger the slowpath on each safepoint and on each allocation.
|
|
||||||
// The slowpath will trigger GC if this thread didn't meet this safepoint/allocation site before.
|
|
||||||
config.threshold = 1;
|
|
||||||
config.allocationThresholdBytes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override {
|
|
||||||
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
|
|
||||||
if (heapGrowthController_.NeedsGC()) {
|
|
||||||
scheduleGC_();
|
|
||||||
} else if (safePointTracker_.registerCurrentSafePoint(1)) {
|
|
||||||
scheduleGC_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnPerformFullGC() noexcept override { heapGrowthController_.OnPerformFullGC(); }
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::function<void()> scheduleGC_;
|
|
||||||
HeapGrowthController heapGrowthController_;
|
|
||||||
SafePointTracker<> safePointTracker_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace kotlin::gc::internal
|
|
||||||
@@ -1,877 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "GCScheduler.hpp"
|
|
||||||
|
|
||||||
#include <future>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "gmock/gmock.h"
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
|
|
||||||
#include "AppStateTrackingTestSupport.hpp"
|
|
||||||
#include "ClockTestSupport.hpp"
|
|
||||||
#include "GCSchedulerImpl.hpp"
|
|
||||||
#include "SingleThreadExecutor.hpp"
|
|
||||||
#include "TestSupport.hpp"
|
|
||||||
#include "std_support/Vector.hpp"
|
|
||||||
|
|
||||||
using namespace kotlin;
|
|
||||||
|
|
||||||
using testing::_;
|
|
||||||
|
|
||||||
namespace kotlin {
|
|
||||||
namespace gc {
|
|
||||||
|
|
||||||
class GCSchedulerThreadDataTestApi : private Pinned {
|
|
||||||
public:
|
|
||||||
explicit GCSchedulerThreadDataTestApi(GCSchedulerThreadData& scheduler) : scheduler_(scheduler) {}
|
|
||||||
|
|
||||||
void OnSafePointRegularImpl(size_t weight) { scheduler_.OnSafePointRegularImpl(weight); }
|
|
||||||
|
|
||||||
void SetAllocatedBytes(size_t bytes) { scheduler_.allocatedBytes_ = bytes; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
GCSchedulerThreadData& scheduler_;
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, RegularSafePoint) {
|
|
||||||
constexpr size_t kWeight = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kThreshold = kCount * kWeight;
|
|
||||||
testing::MockFunction<void(GCSchedulerThreadData&)> slowPath;
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = 1;
|
|
||||||
config.threshold = kThreshold;
|
|
||||||
GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold);
|
|
||||||
});
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), kWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, AllocationSafePoint) {
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(GCSchedulerThreadData&)> slowPath;
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
config.threshold = 1;
|
|
||||||
GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
});
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kSize);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, ResetByGC) {
|
|
||||||
constexpr size_t kWeight = 2;
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kThreshold = kCount * kWeight;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(GCSchedulerThreadData&)> slowPath;
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
config.threshold = kThreshold;
|
|
||||||
GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
scheduler.OnStoppedForGC();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterResetByGC) {
|
|
||||||
constexpr size_t kWeight = 2;
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kThreshold = kCount * kWeight;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(GCSchedulerThreadData&)> slowPath;
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
config.threshold = kThreshold;
|
|
||||||
GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold - kSize;
|
|
||||||
config.threshold = kThreshold - kWeight;
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
scheduler.OnStoppedForGC();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
|
||||||
});
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
});
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterRegularSafePoint) {
|
|
||||||
constexpr size_t kWeight = 2;
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kThreshold = kCount * kWeight;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(GCSchedulerThreadData&)> slowPath;
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
config.threshold = kThreshold;
|
|
||||||
GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold - kSize;
|
|
||||||
config.threshold = kThreshold - kWeight;
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold);
|
|
||||||
});
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
|
||||||
});
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
});
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterAllocationSafePoint) {
|
|
||||||
constexpr size_t kWeight = 2;
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kThreshold = kCount * kWeight;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(GCSchedulerThreadData&)> slowPath;
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
config.threshold = kThreshold;
|
|
||||||
GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold - kSize;
|
|
||||||
config.threshold = kThreshold - kWeight;
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold);
|
|
||||||
});
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
|
||||||
});
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
});
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MutatorThread : private Pinned {
|
|
||||||
public:
|
|
||||||
MutatorThread(GCSchedulerConfig& config, std::function<void(GCSchedulerThreadData&)> slowPath) :
|
|
||||||
executor_([&config, slowPath = std::move(slowPath)] { return Context(config, std::move(slowPath)); }) {}
|
|
||||||
|
|
||||||
std::future<void> Allocate(size_t bytes) {
|
|
||||||
return executor_.execute([&, bytes] {
|
|
||||||
auto& context = executor_.context();
|
|
||||||
context.threadDataTestApi.SetAllocatedBytes(bytes);
|
|
||||||
context.slowPath(context.threadData);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Context {
|
|
||||||
GCSchedulerThreadData threadData;
|
|
||||||
GCSchedulerThreadDataTestApi threadDataTestApi;
|
|
||||||
std::function<void(GCSchedulerThreadData&)> slowPath;
|
|
||||||
|
|
||||||
Context(GCSchedulerConfig& config, std::function<void(GCSchedulerThreadData&)> slowPath) :
|
|
||||||
threadData(config, [](GCSchedulerThreadData&) {}), threadDataTestApi(threadData), slowPath(slowPath) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
SingleThreadExecutor<Context> executor_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename GCScheduler, int MutatorCount>
|
|
||||||
class GCSchedulerDataTestApi {
|
|
||||||
public:
|
|
||||||
explicit GCSchedulerDataTestApi(GCSchedulerConfig& config) : scheduler_(config, scheduleGC_.AsStdFunction()) {
|
|
||||||
mutators_.reserve(MutatorCount);
|
|
||||||
for (int i = 0; i < MutatorCount; ++i) {
|
|
||||||
mutators_.emplace_back(std_support::make_unique<MutatorThread>(
|
|
||||||
config, [this](GCSchedulerThreadData& threadData) { scheduler_.UpdateFromThreadData(threadData); }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::future<void> Allocate(int mutator, size_t bytes) { return mutators_[mutator]->Allocate(bytes); }
|
|
||||||
|
|
||||||
void OnPerformFullGC() { scheduler_.OnPerformFullGC(); }
|
|
||||||
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) { scheduler_.UpdateAliveSetBytes(bytes); }
|
|
||||||
|
|
||||||
testing::MockFunction<void()>& scheduleGC() { return scheduleGC_; }
|
|
||||||
|
|
||||||
template <typename Duration>
|
|
||||||
void advance_time(Duration duration) {
|
|
||||||
test_support::manual_clock::sleep_for(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std_support::vector<std_support::unique_ptr<MutatorThread>> mutators_;
|
|
||||||
testing::MockFunction<void()> scheduleGC_;
|
|
||||||
GCScheduler scheduler_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <int MutatorCount>
|
|
||||||
using GCSchedulerDataOnSafepointsTestApi =
|
|
||||||
GCSchedulerDataTestApi<gc::internal::GCSchedulerDataOnSafepoints<test_support::manual_clock>, MutatorCount>;
|
|
||||||
|
|
||||||
template <int MutatorCount>
|
|
||||||
using GCSchedulerDataWithTimerTestApi =
|
|
||||||
GCSchedulerDataTestApi<gc::internal::GCSchedulerDataWithTimer<test_support::manual_clock>, MutatorCount>;
|
|
||||||
|
|
||||||
class GCSchedulerDataTest : public ::testing::Test {
|
|
||||||
public:
|
|
||||||
GCSchedulerDataTest() { test_support::manual_clock::reset(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
class GCSchedulerDataOnSafePointsTest : public GCSchedulerDataTest {};
|
|
||||||
class GCSchedulerDataWithTimerTest : public GCSchedulerDataTest {};
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataOnSafePointsTest, CollectOnTargetHeapReached) {
|
|
||||||
test_support::manual_clock::reset();
|
|
||||||
|
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = (mutatorsCount + 1) * 10;
|
|
||||||
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
|
||||||
std_support::vector<std::future<void>> futures;
|
|
||||||
for (int i = 0; i < mutatorsCount; ++i) {
|
|
||||||
futures.push_back(schedulerTestApi.Allocate(i, 10));
|
|
||||||
}
|
|
||||||
for (auto& future : futures) {
|
|
||||||
future.get();
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, mutatorsCount * 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataOnSafePointsTest, CollectOnTimeoutReached) {
|
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = std::numeric_limits<size_t>::max();
|
|
||||||
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
|
||||||
schedulerTestApi.advance_time(microseconds(5));
|
|
||||||
std_support::vector<std::future<void>> futures;
|
|
||||||
for (int i = 0; i < mutatorsCount; ++i) {
|
|
||||||
futures.push_back(schedulerTestApi.Allocate(i, 0));
|
|
||||||
}
|
|
||||||
for (auto& future : futures) {
|
|
||||||
future.get();
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.advance_time(microseconds(5));
|
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataOnSafePointsTest, FullTimeoutAfterLastGC) {
|
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = 10;
|
|
||||||
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.advance_time(microseconds(5));
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
|
||||||
schedulerTestApi.advance_time(microseconds(5));
|
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
|
||||||
// It's now 10 us since the start, but only 5 us since previous collection.
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.advance_time(microseconds(5));
|
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
|
||||||
// It's now 10 us since the previous collection.
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataOnSafePointsTest, DoNotTuneTargetHeap) {
|
|
||||||
constexpr int mutatorsCount = 1;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = 10;
|
|
||||||
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(10);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataOnSafePointsTest, TuneTargetHeap) {
|
|
||||||
constexpr int mutatorsCount = 1;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = true;
|
|
||||||
config.targetHeapBytes = 10;
|
|
||||||
config.targetHeapUtilization = 0.5;
|
|
||||||
config.minHeapBytes = 5;
|
|
||||||
config.maxHeapBytes = 50;
|
|
||||||
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(10);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 20);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
// For a total heap of 20.
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(20);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 40);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
// For a total heap of 60.
|
|
||||||
schedulerTestApi.Allocate(0, 40).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(60);
|
|
||||||
|
|
||||||
// But we will keep the 50, which means we will trigger GC every allocation, until alive set falls down
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
// Keeping total heap of 60.
|
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(60);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
// Dropping to 40
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(40);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
// For a total heap of 50
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
// Dropping to 1
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(1);
|
|
||||||
|
|
||||||
// But the minimum is set to 5.
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataWithTimerTest, CollectOnTargetHeapReached) {
|
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = (mutatorsCount + 1) * 10;
|
|
||||||
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
|
||||||
std_support::vector<std::future<void>> futures;
|
|
||||||
for (int i = 0; i < mutatorsCount; ++i) {
|
|
||||||
futures.push_back(schedulerTestApi.Allocate(i, 10));
|
|
||||||
}
|
|
||||||
for (auto& future : futures) {
|
|
||||||
future.get();
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, mutatorsCount * 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataWithTimerTest, CollectOnTimeoutReached) {
|
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = std::numeric_limits<size_t>::max();
|
|
||||||
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
// Wait until the timer is initialized.
|
|
||||||
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.advance_time(microseconds(10));
|
|
||||||
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataWithTimerTest, FullTimeoutAfterLastGC) {
|
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = 10;
|
|
||||||
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
// Wait until the timer is initialized.
|
|
||||||
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
|
||||||
|
|
||||||
schedulerTestApi.advance_time(microseconds(5));
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
|
|
||||||
// pending should restart to be 10us since the previous collection without scheduling another GC.
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
|
||||||
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataWithTimerTest, DoNotTuneTargetHeap) {
|
|
||||||
constexpr int mutatorsCount = 1;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = 10;
|
|
||||||
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(10);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataWithTimerTest, TuneTargetHeap) {
|
|
||||||
constexpr int mutatorsCount = 1;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = true;
|
|
||||||
config.targetHeapBytes = 10;
|
|
||||||
config.targetHeapUtilization = 0.5;
|
|
||||||
config.minHeapBytes = 5;
|
|
||||||
config.maxHeapBytes = 50;
|
|
||||||
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(10);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 20);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
// For a total heap of 20.
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(20);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 40);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
// For a total heap of 60.
|
|
||||||
schedulerTestApi.Allocate(0, 40).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(60);
|
|
||||||
|
|
||||||
// But we will keep the 50, which means we will trigger GC every allocation, until alive set falls down
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
// Keeping total heap of 60.
|
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(60);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
// Dropping to 40
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(40);
|
|
||||||
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
// For a total heap of 50
|
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
// Dropping to 1
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(1);
|
|
||||||
|
|
||||||
// But the minimum is set to 5.
|
|
||||||
EXPECT_THAT(config.targetHeapBytes.load(), 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GCSchedulerDataWithTimerTest, DoNotCollectOnTimerInBackground) {
|
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
config.regularGcIntervalMicroseconds = 10;
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = std::numeric_limits<size_t>::max();
|
|
||||||
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
|
||||||
|
|
||||||
// TODO: Not a global, please.
|
|
||||||
mm::AppStateTrackingTestSupport appStateTracking(mm::GlobalData::Instance().appStateTracking());
|
|
||||||
|
|
||||||
// Wait until the timer is initialized.
|
|
||||||
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
|
||||||
|
|
||||||
// Now go into the background.
|
|
||||||
ASSERT_THAT(mm::GlobalData::Instance().appStateTracking().state(), mm::AppStateTracking::State::kForeground);
|
|
||||||
appStateTracking.setState(mm::AppStateTracking::State::kBackground);
|
|
||||||
|
|
||||||
// Timer works in the background, but does nothing.
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
|
||||||
schedulerTestApi.advance_time(microseconds(10));
|
|
||||||
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
// Now go back into the foreground.
|
|
||||||
appStateTracking.setState(mm::AppStateTracking::State::kForeground);
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
|
||||||
schedulerTestApi.advance_time(microseconds(10));
|
|
||||||
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
schedulerTestApi.OnPerformFullGC();
|
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// These tests require a stack trace to contain call site addresses but
|
|
||||||
// on Windows a trace contains function addresses instead.
|
|
||||||
// So skip these tests on Windows.
|
|
||||||
#if (__MINGW32__ || __MINGW64__)
|
|
||||||
#define SKIP_ON_WINDOWS() do { GTEST_SKIP() << "Skip on Windows"; } while(false)
|
|
||||||
#else
|
|
||||||
#define SKIP_ON_WINDOWS() do { } while(false)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST(SafePointTrackerTest, RegisterSafePoints) {
|
|
||||||
SKIP_ON_WINDOWS();
|
|
||||||
[]() OPTNONE {
|
|
||||||
internal::SafePointTracker<> tracker;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < 10; i++) {
|
|
||||||
bool registered1 = tracker.registerCurrentSafePoint(0);
|
|
||||||
bool registered2 = tracker.registerCurrentSafePoint(0);
|
|
||||||
|
|
||||||
bool expected = (i == 0);
|
|
||||||
|
|
||||||
EXPECT_THAT(registered1, expected);
|
|
||||||
EXPECT_THAT(registered2, expected);
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <size_t SafePointStackSize>
|
|
||||||
OPTNONE bool registerCurrentSafePoint(internal::SafePointTracker<SafePointStackSize>& tracker) {
|
|
||||||
return tracker.registerCurrentSafePoint(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SafePointTrackerTest, TrackTopFramesOnly) {
|
|
||||||
SKIP_ON_WINDOWS();
|
|
||||||
[]() OPTNONE {
|
|
||||||
internal::SafePointTracker<16> longTracker;
|
|
||||||
internal::SafePointTracker<1> shortTracker;
|
|
||||||
|
|
||||||
bool longRegistered1 = registerCurrentSafePoint(longTracker);
|
|
||||||
bool longRegistered2 = registerCurrentSafePoint(longTracker);
|
|
||||||
|
|
||||||
EXPECT_THAT(longRegistered1, true);
|
|
||||||
EXPECT_THAT(longRegistered2, true);
|
|
||||||
|
|
||||||
bool shortRegistered1 = registerCurrentSafePoint(shortTracker);
|
|
||||||
bool shortRegistered2 = registerCurrentSafePoint(shortTracker);
|
|
||||||
|
|
||||||
EXPECT_THAT(shortRegistered1, true);
|
|
||||||
EXPECT_THAT(shortRegistered2, false);
|
|
||||||
}();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SafePointTrackerTest, CleanOnSizeLimit) {
|
|
||||||
SKIP_ON_WINDOWS();
|
|
||||||
[]() OPTNONE {
|
|
||||||
internal::SafePointTracker<> tracker(2);
|
|
||||||
|
|
||||||
ASSERT_THAT(tracker.size(), 0);
|
|
||||||
ASSERT_THAT(tracker.maxSize(), 2);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < 3; i++) {
|
|
||||||
bool registered1 = tracker.registerCurrentSafePoint(0);
|
|
||||||
|
|
||||||
EXPECT_THAT(registered1, true);
|
|
||||||
EXPECT_THAT(tracker.size(), 1);
|
|
||||||
|
|
||||||
bool registered2 = tracker.registerCurrentSafePoint(0);
|
|
||||||
|
|
||||||
EXPECT_THAT(registered2, true);
|
|
||||||
EXPECT_THAT(tracker.size(), 2);
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(AggressiveSchedulerTest, TriggerGCOnUniqueSafePoint) {
|
|
||||||
SKIP_ON_WINDOWS();
|
|
||||||
[]() OPTNONE {
|
|
||||||
testing::MockFunction<void()> scheduleGC;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
gc::internal::GCSchedulerDataAggressive scheduler(config, scheduleGC.AsStdFunction());
|
|
||||||
ASSERT_EQ(config.threshold, 1);
|
|
||||||
|
|
||||||
GCSchedulerThreadData threadSchedulerData(config, [](GCSchedulerThreadData&){});
|
|
||||||
|
|
||||||
EXPECT_CALL(scheduleGC, Call()).Times(1);
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
scheduler.UpdateFromThreadData(threadSchedulerData);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&scheduleGC);
|
|
||||||
|
|
||||||
EXPECT_CALL(scheduleGC, Call()).Times(1);
|
|
||||||
scheduler.UpdateFromThreadData(threadSchedulerData);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&scheduleGC);
|
|
||||||
}();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(AggressiveSchedulerTest, TriggerGCOnAllocationThreshold) {
|
|
||||||
SKIP_ON_WINDOWS();
|
|
||||||
[]() OPTNONE {
|
|
||||||
testing::MockFunction<void()> scheduleGC;
|
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
|
||||||
gc::internal::GCSchedulerDataAggressive scheduler(config, scheduleGC.AsStdFunction());
|
|
||||||
GCSchedulerThreadData threadSchedulerData(config, [&scheduler](GCSchedulerThreadData& data){
|
|
||||||
scheduler.UpdateFromThreadData(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
ASSERT_EQ(config.allocationThresholdBytes, 1);
|
|
||||||
|
|
||||||
config.autoTune = false;
|
|
||||||
config.targetHeapBytes = 10;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
// We trigger GC on the first iteration, when the unique allocation point is faced,
|
|
||||||
// and on the last iteration when target heap size is reached.
|
|
||||||
EXPECT_CALL(scheduleGC, Call())
|
|
||||||
.WillOnce([&i]() { EXPECT_THAT(i, 0); })
|
|
||||||
.WillOnce([&i]() { EXPECT_THAT(i, 9); });
|
|
||||||
|
|
||||||
for (; i < 10; i++) {
|
|
||||||
threadSchedulerData.OnSafePointAllocation(1);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&scheduleGC);
|
|
||||||
}();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace gc
|
|
||||||
} // namespace kotlin
|
|
||||||
@@ -169,7 +169,7 @@ typename Traits::ObjectFactory::FinalizerQueue Sweep(GCHandle handle, typename T
|
|||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
void collectRootSetForThread(GCHandle gcHandle, typename Traits::MarkQueue& markQueue, mm::ThreadData& thread) {
|
void collectRootSetForThread(GCHandle gcHandle, typename Traits::MarkQueue& markQueue, mm::ThreadData& thread) {
|
||||||
auto handle = gcHandle.collectThreadRoots(thread);
|
auto handle = gcHandle.collectThreadRoots(thread);
|
||||||
thread.gc().OnStoppedForGC();
|
thread.gcScheduler().OnStoppedForGC();
|
||||||
// TODO: Remove useless mm::ThreadRootSet abstraction.
|
// TODO: Remove useless mm::ThreadRootSet abstraction.
|
||||||
for (auto value : mm::ThreadRootSet(thread)) {
|
for (auto value : mm::ThreadRootSet(thread)) {
|
||||||
if (internal::collectRoot<Traits>(markQueue, value.object)) {
|
if (internal::collectRoot<Traits>(markQueue, value.object)) {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : impl_(std_support::make_unique<Impl>(gc, threadData)) {}
|
gc::GC::ThreadData::ThreadData(GC& gc, gcScheduler::GCSchedulerThreadData&, mm::ThreadData& threadData) noexcept :
|
||||||
|
impl_(std_support::make_unique<Impl>(gc, threadData)) {}
|
||||||
|
|
||||||
gc::GC::ThreadData::~ThreadData() = default;
|
gc::GC::ThreadData::~ThreadData() = default;
|
||||||
|
|
||||||
@@ -52,13 +53,9 @@ ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeI
|
|||||||
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::ThreadData::OnStoppedForGC() noexcept {
|
|
||||||
impl_->gcScheduler().OnStoppedForGC();
|
|
||||||
}
|
|
||||||
|
|
||||||
void gc::GC::ThreadData::OnSuspendForGC() noexcept { }
|
void gc::GC::ThreadData::OnSuspendForGC() noexcept { }
|
||||||
|
|
||||||
gc::GC::GC() noexcept : impl_(std_support::make_unique<Impl>()) {}
|
gc::GC::GC(gcScheduler::GCScheduler&) noexcept : impl_(std_support::make_unique<Impl>()) {}
|
||||||
|
|
||||||
gc::GC::~GC() = default;
|
gc::GC::~GC() = default;
|
||||||
|
|
||||||
@@ -71,10 +68,6 @@ size_t gc::GC::GetTotalHeapObjectsSizeBytes() const noexcept {
|
|||||||
return allocatedBytes();
|
return allocatedBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
|
||||||
return impl_->gcScheduler().config();
|
|
||||||
}
|
|
||||||
|
|
||||||
void gc::GC::ClearForTests() noexcept {
|
void gc::GC::ClearForTests() noexcept {
|
||||||
impl_->objectFactory().ClearForTests();
|
impl_->objectFactory().ClearForTests();
|
||||||
GCHandle::ClearForTests();
|
GCHandle::ClearForTests();
|
||||||
@@ -96,3 +89,5 @@ ALWAYS_INLINE void gc::GC::processArrayInMark(void* state, ArrayHeader* array) n
|
|||||||
|
|
||||||
// static
|
// static
|
||||||
ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {}
|
ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {}
|
||||||
|
|
||||||
|
void gc::GC::Schedule() noexcept {}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "GC.hpp"
|
#include "GC.hpp"
|
||||||
|
|
||||||
#include "NoOpGC.hpp"
|
#include "NoOpGC.hpp"
|
||||||
|
#include "ObjectFactory.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
namespace gc {
|
namespace gc {
|
||||||
@@ -16,31 +17,25 @@ using GCImpl = NoOpGC;
|
|||||||
|
|
||||||
class GC::Impl : private Pinned {
|
class GC::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
Impl() noexcept : gc_(objectFactory_, gcScheduler_) {}
|
Impl() noexcept = default;
|
||||||
|
|
||||||
mm::ObjectFactory<gc::GCImpl>& objectFactory() noexcept { return objectFactory_; }
|
mm::ObjectFactory<gc::GCImpl>& objectFactory() noexcept { return objectFactory_; }
|
||||||
GCScheduler& gcScheduler() noexcept { return gcScheduler_; }
|
|
||||||
GCImpl& gc() noexcept { return gc_; }
|
GCImpl& gc() noexcept { return gc_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mm::ObjectFactory<gc::GCImpl> objectFactory_;
|
mm::ObjectFactory<gc::GCImpl> objectFactory_;
|
||||||
GCScheduler gcScheduler_;
|
|
||||||
GCImpl gc_;
|
GCImpl gc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GC::ThreadData::Impl : private Pinned {
|
class GC::ThreadData::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
Impl(GC& gc, mm::ThreadData& threadData) noexcept :
|
Impl(GC& gc, mm::ThreadData& threadData) noexcept :
|
||||||
gcScheduler_(gc.impl_->gcScheduler().NewThreadData()),
|
|
||||||
gc_(gc.impl_->gc(), threadData, gcScheduler_),
|
|
||||||
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()) {}
|
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()) {}
|
||||||
|
|
||||||
GCSchedulerThreadData& gcScheduler() noexcept { return gcScheduler_; }
|
|
||||||
GCImpl::ThreadData& gc() noexcept { return gc_; }
|
GCImpl::ThreadData& gc() noexcept { return gc_; }
|
||||||
mm::ObjectFactory<GCImpl>::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
|
mm::ObjectFactory<GCImpl>::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GCSchedulerThreadData gcScheduler_;
|
|
||||||
GCImpl::ThreadData gc_;
|
GCImpl::ThreadData gc_;
|
||||||
mm::ObjectFactory<GCImpl>::ThreadQueue objectFactoryThreadQueue_;
|
mm::ObjectFactory<GCImpl>::ThreadQueue objectFactoryThreadQueue_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,10 +9,8 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
#include "Allocator.hpp"
|
#include "Allocator.hpp"
|
||||||
#include "GCScheduler.hpp"
|
#include "Logging.hpp"
|
||||||
#include "ObjectFactory.hpp"
|
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "Types.h"
|
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
@@ -34,7 +32,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
using ObjectData = NoOpGC::ObjectData;
|
using ObjectData = NoOpGC::ObjectData;
|
||||||
|
|
||||||
ThreadData(NoOpGC& gc, mm::ThreadData& threadData, GCSchedulerThreadData&) noexcept {}
|
ThreadData() noexcept {}
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
void SafePointFunctionPrologue() noexcept {}
|
void SafePointFunctionPrologue() noexcept {}
|
||||||
@@ -52,15 +50,10 @@ public:
|
|||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
NoOpGC(mm::ObjectFactory<NoOpGC>&, GCScheduler&) noexcept {
|
NoOpGC() noexcept { RuntimeLogInfo({kTagGC}, "No-op GC initialized"); }
|
||||||
RuntimeLogDebug({kTagGC}, "No-op GC initialized");
|
|
||||||
}
|
|
||||||
~NoOpGC() = default;
|
~NoOpGC() = default;
|
||||||
|
|
||||||
GCScheduler& scheduler() noexcept { return scheduler_; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GCScheduler scheduler_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gc
|
} // namespace gc
|
||||||
|
|||||||
@@ -13,25 +13,17 @@
|
|||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
namespace {
|
gc::GC::ThreadData::ThreadData(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
||||||
|
impl_(std_support::make_unique<Impl>(gc, gcScheduler, threadData)) {}
|
||||||
ALWAYS_INLINE void SafePointRegular(gc::GC::ThreadData& threadData, size_t weight) noexcept {
|
|
||||||
threadData.impl().gcScheduler().OnSafePointRegular(weight);
|
|
||||||
mm::SuspendIfRequested();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : impl_(std_support::make_unique<Impl>(gc, threadData)) {}
|
|
||||||
|
|
||||||
gc::GC::ThreadData::~ThreadData() = default;
|
gc::GC::ThreadData::~ThreadData() = default;
|
||||||
|
|
||||||
ALWAYS_INLINE void gc::GC::ThreadData::SafePointFunctionPrologue() noexcept {
|
ALWAYS_INLINE void gc::GC::ThreadData::SafePointFunctionPrologue() noexcept {
|
||||||
SafePointRegular(*this, GCSchedulerThreadData::kFunctionPrologueWeight);
|
mm::SuspendIfRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE void gc::GC::ThreadData::SafePointLoopBody() noexcept {
|
ALWAYS_INLINE void gc::GC::ThreadData::SafePointLoopBody() noexcept {
|
||||||
SafePointRegular(*this, GCSchedulerThreadData::kLoopBodyWeight);
|
mm::SuspendIfRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::ThreadData::Schedule() noexcept {
|
void gc::GC::ThreadData::Schedule() noexcept {
|
||||||
@@ -62,13 +54,9 @@ ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeI
|
|||||||
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::GC::ThreadData::OnStoppedForGC() noexcept {
|
|
||||||
impl_->gcScheduler().OnStoppedForGC();
|
|
||||||
}
|
|
||||||
|
|
||||||
void gc::GC::ThreadData::OnSuspendForGC() noexcept { }
|
void gc::GC::ThreadData::OnSuspendForGC() noexcept { }
|
||||||
|
|
||||||
gc::GC::GC() noexcept : impl_(std_support::make_unique<Impl>()) {}
|
gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {}
|
||||||
|
|
||||||
gc::GC::~GC() = default;
|
gc::GC::~GC() = default;
|
||||||
|
|
||||||
@@ -81,10 +69,6 @@ size_t gc::GC::GetTotalHeapObjectsSizeBytes() const noexcept {
|
|||||||
return allocatedBytes();
|
return allocatedBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
|
|
||||||
return impl_->gcScheduler().config();
|
|
||||||
}
|
|
||||||
|
|
||||||
void gc::GC::ClearForTests() noexcept {
|
void gc::GC::ClearForTests() noexcept {
|
||||||
impl_->gc().StopFinalizerThreadIfRunning();
|
impl_->gc().StopFinalizerThreadIfRunning();
|
||||||
impl_->objectFactory().ClearForTests();
|
impl_->objectFactory().ClearForTests();
|
||||||
@@ -117,3 +101,7 @@ ALWAYS_INLINE void gc::GC::processArrayInMark(void* state, ArrayHeader* array) n
|
|||||||
ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {
|
ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {
|
||||||
gc::internal::processFieldInMark<gc::internal::MarkTraits>(state, field);
|
gc::internal::processFieldInMark<gc::internal::MarkTraits>(state, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gc::GC::Schedule() noexcept {
|
||||||
|
impl_->gc().Schedule();
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,31 +16,25 @@ using GCImpl = SameThreadMarkAndSweep;
|
|||||||
|
|
||||||
class GC::Impl : private Pinned {
|
class GC::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
Impl() noexcept : gc_(objectFactory_, gcScheduler_) {}
|
explicit Impl(gcScheduler::GCScheduler& gcScheduler) noexcept : gc_(objectFactory_, gcScheduler) {}
|
||||||
|
|
||||||
mm::ObjectFactory<gc::GCImpl>& objectFactory() noexcept { return objectFactory_; }
|
mm::ObjectFactory<gc::GCImpl>& objectFactory() noexcept { return objectFactory_; }
|
||||||
GCScheduler& gcScheduler() noexcept { return gcScheduler_; }
|
|
||||||
GCImpl& gc() noexcept { return gc_; }
|
GCImpl& gc() noexcept { return gc_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mm::ObjectFactory<gc::GCImpl> objectFactory_;
|
mm::ObjectFactory<gc::GCImpl> objectFactory_;
|
||||||
GCScheduler gcScheduler_;
|
|
||||||
GCImpl gc_;
|
GCImpl gc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GC::ThreadData::Impl : private Pinned {
|
class GC::ThreadData::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
Impl(GC& gc, mm::ThreadData& threadData) noexcept :
|
Impl(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
||||||
gcScheduler_(gc.impl_->gcScheduler().NewThreadData()),
|
gc_(gc.impl_->gc(), threadData, gcScheduler), objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()) {}
|
||||||
gc_(gc.impl_->gc(), threadData, gcScheduler_),
|
|
||||||
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()) {}
|
|
||||||
|
|
||||||
GCSchedulerThreadData& gcScheduler() noexcept { return gcScheduler_; }
|
|
||||||
GCImpl::ThreadData& gc() noexcept { return gc_; }
|
GCImpl::ThreadData& gc() noexcept { return gc_; }
|
||||||
mm::ObjectFactory<GCImpl>::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
|
mm::ObjectFactory<GCImpl>::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GCSchedulerThreadData gcScheduler_;
|
|
||||||
GCImpl::ThreadData gc_;
|
GCImpl::ThreadData gc_;
|
||||||
mm::ObjectFactory<GCImpl>::ThreadQueue objectFactoryThreadQueue_;
|
mm::ObjectFactory<GCImpl>::ThreadQueue objectFactoryThreadQueue_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,17 +84,11 @@ void gc::SameThreadMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gc::SameThreadMarkAndSweep::SameThreadMarkAndSweep(
|
gc::SameThreadMarkAndSweep::SameThreadMarkAndSweep(
|
||||||
mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory, GCScheduler& gcScheduler) noexcept :
|
mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory, gcScheduler::GCScheduler& gcScheduler) noexcept :
|
||||||
objectFactory_(objectFactory), gcScheduler_(gcScheduler), finalizerProcessor_([this](int64_t epoch) noexcept {
|
objectFactory_(objectFactory), gcScheduler_(gcScheduler), finalizerProcessor_([this](int64_t epoch) noexcept {
|
||||||
GCHandle::getByEpoch(epoch).finalizersDone();
|
GCHandle::getByEpoch(epoch).finalizersDone();
|
||||||
state_.finalized(epoch);
|
state_.finalized(epoch);
|
||||||
}) {
|
}) {
|
||||||
gcScheduler_.SetScheduleGC([this]() NO_INLINE {
|
|
||||||
RuntimeLogDebug({kTagGC}, "Scheduling GC by thread %d", konan::currentThreadId());
|
|
||||||
// This call acquires a lock, so we need to ensure that we're in the safe state.
|
|
||||||
NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true);
|
|
||||||
state_.schedule();
|
|
||||||
});
|
|
||||||
gcThread_ = ScopedThread(ScopedThread::attributes().name("GC thread"), [this] {
|
gcThread_ = ScopedThread(ScopedThread::attributes().name("GC thread"), [this] {
|
||||||
while (true) {
|
while (true) {
|
||||||
auto epoch = state_.waitScheduled();
|
auto epoch = state_.waitScheduled();
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public:
|
|||||||
using ObjectData = SameThreadMarkAndSweep::ObjectData;
|
using ObjectData = SameThreadMarkAndSweep::ObjectData;
|
||||||
using Allocator = AllocatorWithGC<Allocator, ThreadData>;
|
using Allocator = AllocatorWithGC<Allocator, ThreadData>;
|
||||||
|
|
||||||
ThreadData(SameThreadMarkAndSweep& gc, mm::ThreadData& threadData, GCSchedulerThreadData& gcScheduler) noexcept :
|
ThreadData(SameThreadMarkAndSweep& gc, mm::ThreadData& threadData, gcScheduler::GCSchedulerThreadData& gcScheduler) noexcept :
|
||||||
gc_(gc), gcScheduler_(gcScheduler) {}
|
gc_(gc), gcScheduler_(gcScheduler) {}
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
SameThreadMarkAndSweep& gc_;
|
SameThreadMarkAndSweep& gc_;
|
||||||
GCSchedulerThreadData& gcScheduler_;
|
gcScheduler::GCSchedulerThreadData& gcScheduler_;
|
||||||
};
|
};
|
||||||
|
|
||||||
using Allocator = ThreadData::Allocator;
|
using Allocator = ThreadData::Allocator;
|
||||||
@@ -95,18 +95,20 @@ public:
|
|||||||
using FinalizerQueue = mm::ObjectFactory<SameThreadMarkAndSweep>::FinalizerQueue;
|
using FinalizerQueue = mm::ObjectFactory<SameThreadMarkAndSweep>::FinalizerQueue;
|
||||||
using FinalizerQueueTraits = mm::ObjectFactory<SameThreadMarkAndSweep>::FinalizerQueueTraits;
|
using FinalizerQueueTraits = mm::ObjectFactory<SameThreadMarkAndSweep>::FinalizerQueueTraits;
|
||||||
|
|
||||||
SameThreadMarkAndSweep(mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory, GCScheduler& gcScheduler) noexcept;
|
SameThreadMarkAndSweep(mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory, gcScheduler::GCScheduler& gcScheduler) noexcept;
|
||||||
~SameThreadMarkAndSweep();
|
~SameThreadMarkAndSweep();
|
||||||
|
|
||||||
void StartFinalizerThreadIfNeeded() noexcept;
|
void StartFinalizerThreadIfNeeded() noexcept;
|
||||||
void StopFinalizerThreadIfRunning() noexcept;
|
void StopFinalizerThreadIfRunning() noexcept;
|
||||||
bool FinalizersThreadIsRunning() noexcept;
|
bool FinalizersThreadIsRunning() noexcept;
|
||||||
|
|
||||||
|
void Schedule() noexcept { state_.schedule(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PerformFullGC(int64_t epoch) noexcept;
|
void PerformFullGC(int64_t epoch) noexcept;
|
||||||
|
|
||||||
mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory_;
|
mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory_;
|
||||||
GCScheduler& gcScheduler_;
|
gcScheduler::GCScheduler& gcScheduler_;
|
||||||
|
|
||||||
GCStateHolder state_;
|
GCStateHolder state_;
|
||||||
ScopedThread gcThread_;
|
ScopedThread gcThread_;
|
||||||
|
|||||||
@@ -725,7 +725,8 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsCollect) {
|
|||||||
|
|
||||||
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
|
||||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
|
gcFutures[0] = mutators[0].Execute(
|
||||||
|
[](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
|
||||||
|
|
||||||
// Spin until thread suspension is requested.
|
// Spin until thread suspension is requested.
|
||||||
while (!mm::IsThreadSuspensionRequested()) {
|
while (!mm::IsThreadSuspensionRequested()) {
|
||||||
@@ -857,7 +858,8 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsAddToRootSetAfterCollectionRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
|
gcFutures[0] = mutators[0].Execute(
|
||||||
|
[](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
|
||||||
|
|
||||||
// Spin until thread suspension is requested.
|
// Spin until thread suspension is requested.
|
||||||
while (!mm::IsThreadSuspensionRequested()) {
|
while (!mm::IsThreadSuspensionRequested()) {
|
||||||
@@ -922,7 +924,8 @@ TEST_F(SameThreadMarkAndSweepTest, CrossThreadReference) {
|
|||||||
|
|
||||||
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
|
||||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
|
gcFutures[0] = mutators[0].Execute(
|
||||||
|
[](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
|
||||||
|
|
||||||
// Spin until thread suspension is requested.
|
// Spin until thread suspension is requested.
|
||||||
while (!mm::IsThreadSuspensionRequested()) {
|
while (!mm::IsThreadSuspensionRequested()) {
|
||||||
@@ -1035,7 +1038,8 @@ TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
|
|||||||
|
|
||||||
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
std_support::vector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||||
|
|
||||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
|
gcFutures[0] = mutators[0].Execute(
|
||||||
|
[](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGCWithFinalizers(); });
|
||||||
|
|
||||||
// Spin until thread suspension is requested.
|
// Spin until thread suspension is requested.
|
||||||
while (!mm::IsThreadSuspensionRequested()) {
|
while (!mm::IsThreadSuspensionRequested()) {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCSchedulerImpl.hpp"
|
||||||
|
|
||||||
|
#include "GlobalData.hpp"
|
||||||
|
#include "Memory.h"
|
||||||
|
#include "Logging.hpp"
|
||||||
|
#include "Porting.h"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
void gcScheduler::GCSchedulerThreadData::OnSafePointRegular(size_t weight) noexcept {}
|
||||||
|
|
||||||
|
gcScheduler::GCScheduler::GCScheduler() noexcept :
|
||||||
|
gcData_(std_support::make_unique<internal::GCSchedulerDataAdaptive<steady_clock>>(config_, []() noexcept {
|
||||||
|
// This call acquires a lock, so we need to ensure that we're in the safe state.
|
||||||
|
NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true);
|
||||||
|
mm::GlobalData::Instance().gc().Schedule();
|
||||||
|
})) {}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GCScheduler.hpp"
|
||||||
|
|
||||||
|
#include "AppStateTracking.hpp"
|
||||||
|
#include "GCSchedulerConfig.hpp"
|
||||||
|
#include "GlobalData.hpp"
|
||||||
|
#include "HeapGrowthController.hpp"
|
||||||
|
#include "Logging.hpp"
|
||||||
|
#include "RegularIntervalPacer.hpp"
|
||||||
|
#include "RepeatedTimer.hpp"
|
||||||
|
|
||||||
|
namespace kotlin::gcScheduler::internal {
|
||||||
|
|
||||||
|
template <typename Clock>
|
||||||
|
class GCSchedulerDataAdaptive : public GCSchedulerData {
|
||||||
|
public:
|
||||||
|
GCSchedulerDataAdaptive(GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
|
||||||
|
config_(config),
|
||||||
|
scheduleGC_(std::move(scheduleGC)),
|
||||||
|
appStateTracking_(mm::GlobalData::Instance().appStateTracking()),
|
||||||
|
heapGrowthController_(config),
|
||||||
|
regularIntervalPacer_(config),
|
||||||
|
timer_("GC Timer thread", config_.regularGcInterval(), [this] {
|
||||||
|
if (appStateTracking_.state() == mm::AppStateTracking::State::kBackground) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (regularIntervalPacer_.NeedsGC()) {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Scheduling GC by timer");
|
||||||
|
scheduleGC_();
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
RuntimeLogInfo({kTagGC}, "Adaptive GC scheduler initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateFromThreadData(GCSchedulerThreadData& threadData) noexcept override {
|
||||||
|
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
|
||||||
|
if (heapGrowthController_.NeedsGC()) {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Scheduling GC by allocation");
|
||||||
|
scheduleGC_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnPerformFullGC() noexcept override {
|
||||||
|
heapGrowthController_.OnPerformFullGC();
|
||||||
|
regularIntervalPacer_.OnPerformFullGC();
|
||||||
|
timer_.restart(config_.regularGcInterval());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GCSchedulerConfig& config_;
|
||||||
|
std::function<void()> scheduleGC_;
|
||||||
|
mm::AppStateTracking& appStateTracking_;
|
||||||
|
HeapGrowthController heapGrowthController_;
|
||||||
|
RegularIntervalPacer<Clock> regularIntervalPacer_;
|
||||||
|
RepeatedTimer<Clock> timer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin::gcScheduler::internal
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCSchedulerImpl.hpp"
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "AppStateTrackingTestSupport.hpp"
|
||||||
|
#include "ClockTestSupport.hpp"
|
||||||
|
#include "GCSchedulerTestSupport.hpp"
|
||||||
|
#include "SingleThreadExecutor.hpp"
|
||||||
|
#include "TestSupport.hpp"
|
||||||
|
#include "std_support/Vector.hpp"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class MutatorThread : private Pinned {
|
||||||
|
public:
|
||||||
|
MutatorThread(gcScheduler::GCSchedulerConfig& config, std::function<void(gcScheduler::GCSchedulerThreadData&)> slowPath) :
|
||||||
|
executor_([&config, slowPath = std::move(slowPath)] { return Context(config, std::move(slowPath)); }) {}
|
||||||
|
|
||||||
|
std::future<void> Allocate(size_t bytes) {
|
||||||
|
return executor_.execute([&, bytes] {
|
||||||
|
auto& context = executor_.context();
|
||||||
|
context.threadDataTestApi.SetAllocatedBytes(bytes);
|
||||||
|
context.slowPath(context.threadData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Context {
|
||||||
|
gcScheduler::GCSchedulerThreadData threadData;
|
||||||
|
gcScheduler::test_support::GCSchedulerThreadDataTestApi threadDataTestApi;
|
||||||
|
std::function<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
||||||
|
|
||||||
|
Context(gcScheduler::GCSchedulerConfig& config, std::function<void(gcScheduler::GCSchedulerThreadData&)> slowPath) :
|
||||||
|
threadData(config, [](gcScheduler::GCSchedulerThreadData&) {}), threadDataTestApi(threadData), slowPath(slowPath) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
SingleThreadExecutor<Context> executor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <int MutatorCount>
|
||||||
|
class GCSchedulerDataTestApi {
|
||||||
|
public:
|
||||||
|
explicit GCSchedulerDataTestApi(gcScheduler::GCSchedulerConfig& config) : scheduler_(config, scheduleGC_.AsStdFunction()) {
|
||||||
|
mutators_.reserve(MutatorCount);
|
||||||
|
for (int i = 0; i < MutatorCount; ++i) {
|
||||||
|
mutators_.emplace_back(std_support::make_unique<MutatorThread>(
|
||||||
|
config, [this](gcScheduler::GCSchedulerThreadData& threadData) { scheduler_.UpdateFromThreadData(threadData); }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::future<void> Allocate(int mutator, size_t bytes) { return mutators_[mutator]->Allocate(bytes); }
|
||||||
|
|
||||||
|
void OnPerformFullGC() { scheduler_.OnPerformFullGC(); }
|
||||||
|
|
||||||
|
void UpdateAliveSetBytes(size_t bytes) { scheduler_.UpdateAliveSetBytes(bytes); }
|
||||||
|
|
||||||
|
testing::MockFunction<void()>& scheduleGC() { return scheduleGC_; }
|
||||||
|
|
||||||
|
template <typename Duration>
|
||||||
|
void advance_time(Duration duration) {
|
||||||
|
test_support::manual_clock::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std_support::vector<std_support::unique_ptr<MutatorThread>> mutators_;
|
||||||
|
testing::MockFunction<void()> scheduleGC_;
|
||||||
|
gcScheduler::internal::GCSchedulerDataAdaptive<test_support::manual_clock> scheduler_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class AdaptiveSchedulerTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
AdaptiveSchedulerTest() { test_support::manual_clock::reset(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(AdaptiveSchedulerTest, CollectOnTargetHeapReached) {
|
||||||
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
|
config.autoTune = false;
|
||||||
|
config.targetHeapBytes = (mutatorsCount + 1) * 10;
|
||||||
|
GCSchedulerDataTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
||||||
|
std_support::vector<std::future<void>> futures;
|
||||||
|
for (int i = 0; i < mutatorsCount; ++i) {
|
||||||
|
futures.push_back(schedulerTestApi.Allocate(i, 10));
|
||||||
|
}
|
||||||
|
for (auto& future : futures) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
||||||
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
schedulerTestApi.Allocate(0, mutatorsCount * 10).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AdaptiveSchedulerTest, CollectOnTimeoutReached) {
|
||||||
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
|
config.autoTune = false;
|
||||||
|
config.targetHeapBytes = std::numeric_limits<size_t>::max();
|
||||||
|
GCSchedulerDataTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
|
// Wait until the timer is initialized.
|
||||||
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
schedulerTestApi.advance_time(microseconds(10));
|
||||||
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AdaptiveSchedulerTest, FullTimeoutAfterLastGC) {
|
||||||
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
|
config.autoTune = false;
|
||||||
|
config.targetHeapBytes = 10;
|
||||||
|
GCSchedulerDataTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
|
// Wait until the timer is initialized.
|
||||||
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
|
|
||||||
|
schedulerTestApi.advance_time(microseconds(5));
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
|
|
||||||
|
// pending should restart to be 10us since the previous collection without scheduling another GC.
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
||||||
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AdaptiveSchedulerTest, DoNotTuneTargetHeap) {
|
||||||
|
constexpr int mutatorsCount = 1;
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
|
config.autoTune = false;
|
||||||
|
config.targetHeapBytes = 10;
|
||||||
|
GCSchedulerDataTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(10);
|
||||||
|
|
||||||
|
EXPECT_THAT(config.targetHeapBytes.load(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AdaptiveSchedulerTest, TuneTargetHeap) {
|
||||||
|
constexpr int mutatorsCount = 1;
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
|
config.autoTune = true;
|
||||||
|
config.targetHeapBytes = 10;
|
||||||
|
config.targetHeapUtilization = 0.5;
|
||||||
|
config.minHeapBytes = 5;
|
||||||
|
config.maxHeapBytes = 50;
|
||||||
|
GCSchedulerDataTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(10);
|
||||||
|
|
||||||
|
EXPECT_THAT(config.targetHeapBytes.load(), 20);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
// For a total heap of 20.
|
||||||
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(20);
|
||||||
|
|
||||||
|
EXPECT_THAT(config.targetHeapBytes.load(), 40);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
// For a total heap of 60.
|
||||||
|
schedulerTestApi.Allocate(0, 40).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(60);
|
||||||
|
|
||||||
|
// But we will keep the 50, which means we will trigger GC every allocation, until alive set falls down
|
||||||
|
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
// Keeping total heap of 60.
|
||||||
|
schedulerTestApi.Allocate(0, 0).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(60);
|
||||||
|
|
||||||
|
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
schedulerTestApi.Allocate(0, 0).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
// Dropping to 40
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(40);
|
||||||
|
|
||||||
|
EXPECT_THAT(config.targetHeapBytes.load(), 50);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
// For a total heap of 50
|
||||||
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
// Dropping to 1
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(1);
|
||||||
|
|
||||||
|
// But the minimum is set to 5.
|
||||||
|
EXPECT_THAT(config.targetHeapBytes.load(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AdaptiveSchedulerTest, DoNotCollectOnTimerInBackground) {
|
||||||
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
|
config.autoTune = false;
|
||||||
|
config.targetHeapBytes = std::numeric_limits<size_t>::max();
|
||||||
|
GCSchedulerDataTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
|
// TODO: Not a global, please.
|
||||||
|
mm::AppStateTrackingTestSupport appStateTracking(mm::GlobalData::Instance().appStateTracking());
|
||||||
|
|
||||||
|
// Wait until the timer is initialized.
|
||||||
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
|
|
||||||
|
// Now go into the background.
|
||||||
|
ASSERT_THAT(mm::GlobalData::Instance().appStateTracking().state(), mm::AppStateTracking::State::kForeground);
|
||||||
|
appStateTracking.setState(mm::AppStateTracking::State::kBackground);
|
||||||
|
|
||||||
|
// Timer works in the background, but does nothing.
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
||||||
|
schedulerTestApi.advance_time(microseconds(10));
|
||||||
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
|
||||||
|
// Now go back into the foreground.
|
||||||
|
appStateTracking.setState(mm::AppStateTracking::State::kForeground);
|
||||||
|
|
||||||
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
|
schedulerTestApi.advance_time(microseconds(10));
|
||||||
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
schedulerTestApi.OnPerformFullGC();
|
||||||
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCSchedulerImpl.hpp"
|
||||||
|
|
||||||
|
#include "GlobalData.hpp"
|
||||||
|
#include "Memory.h"
|
||||||
|
#include "Logging.hpp"
|
||||||
|
#include "Porting.h"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
void gcScheduler::GCSchedulerThreadData::OnSafePointRegular(size_t weight) noexcept {
|
||||||
|
OnSafePointRegularImpl(weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
gcScheduler::GCScheduler::GCScheduler() noexcept :
|
||||||
|
gcData_(std_support::make_unique<internal::GCSchedulerDataAggressive>(config_, []() noexcept {
|
||||||
|
// This call acquires a lock, so we need to ensure that we're in the safe state.
|
||||||
|
NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true);
|
||||||
|
mm::GlobalData::Instance().gc().Schedule();
|
||||||
|
})) {}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GCScheduler.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "GCSchedulerConfig.hpp"
|
||||||
|
#include "HeapGrowthController.hpp"
|
||||||
|
#include "Logging.hpp"
|
||||||
|
#include "SafePointTracker.hpp"
|
||||||
|
|
||||||
|
namespace kotlin::gcScheduler::internal {
|
||||||
|
|
||||||
|
class GCSchedulerDataAggressive : public GCSchedulerData {
|
||||||
|
public:
|
||||||
|
GCSchedulerDataAggressive(GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
|
||||||
|
scheduleGC_(std::move(scheduleGC)), heapGrowthController_(config) {
|
||||||
|
// Trigger the slowpath on each safepoint and on each allocation.
|
||||||
|
// The slowpath will trigger GC if this thread didn't meet this safepoint/allocation site before.
|
||||||
|
config.threshold = 1;
|
||||||
|
config.allocationThresholdBytes = 1;
|
||||||
|
RuntimeLogInfo({kTagGC}, "Aggressive GC scheduler initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateFromThreadData(GCSchedulerThreadData& threadData) noexcept override {
|
||||||
|
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
|
||||||
|
if (heapGrowthController_.NeedsGC()) {
|
||||||
|
// Still checking allocations: with a long running loop all safepoints
|
||||||
|
// might be "met", so that's the only trigger to not run out of memory.
|
||||||
|
RuntimeLogDebug({kTagGC}, "Scheduling GC by allocation");
|
||||||
|
scheduleGC_();
|
||||||
|
} else if (safePointTracker_.registerCurrentSafePoint(1)) {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Scheduling GC by safepoint");
|
||||||
|
scheduleGC_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnPerformFullGC() noexcept override { heapGrowthController_.OnPerformFullGC(); }
|
||||||
|
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<void()> scheduleGC_;
|
||||||
|
HeapGrowthController heapGrowthController_;
|
||||||
|
SafePointTracker<> safePointTracker_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin::gcScheduler::internal
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCSchedulerImpl.hpp"
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
// These tests require a stack trace to contain call site addresses but
|
||||||
|
// on Windows a trace contains function addresses instead.
|
||||||
|
// So skip these tests on Windows.
|
||||||
|
#if (__MINGW32__ || __MINGW64__)
|
||||||
|
#define SKIP_ON_WINDOWS() \
|
||||||
|
do { \
|
||||||
|
GTEST_SKIP() << "Skip on Windows"; \
|
||||||
|
} while (false)
|
||||||
|
#else
|
||||||
|
#define SKIP_ON_WINDOWS() \
|
||||||
|
do { \
|
||||||
|
} while (false)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST(AggressiveSchedulerTest, TriggerGCOnUniqueSafePoint) {
|
||||||
|
SKIP_ON_WINDOWS();
|
||||||
|
[]() OPTNONE {
|
||||||
|
testing::MockFunction<void()> scheduleGC;
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
gcScheduler::internal::GCSchedulerDataAggressive scheduler(config, scheduleGC.AsStdFunction());
|
||||||
|
ASSERT_EQ(config.threshold, 1);
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerThreadData threadSchedulerData(config, [](gcScheduler::GCSchedulerThreadData&) {});
|
||||||
|
|
||||||
|
EXPECT_CALL(scheduleGC, Call()).Times(1);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
scheduler.UpdateFromThreadData(threadSchedulerData);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&scheduleGC);
|
||||||
|
|
||||||
|
EXPECT_CALL(scheduleGC, Call()).Times(1);
|
||||||
|
scheduler.UpdateFromThreadData(threadSchedulerData);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&scheduleGC);
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AggressiveSchedulerTest, TriggerGCOnAllocationThreshold) {
|
||||||
|
SKIP_ON_WINDOWS();
|
||||||
|
[]() OPTNONE {
|
||||||
|
testing::MockFunction<void()> scheduleGC;
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
gcScheduler::internal::GCSchedulerDataAggressive scheduler(config, scheduleGC.AsStdFunction());
|
||||||
|
gcScheduler::GCSchedulerThreadData threadSchedulerData(
|
||||||
|
config, [&scheduler](gcScheduler::GCSchedulerThreadData& data) { scheduler.UpdateFromThreadData(data); });
|
||||||
|
|
||||||
|
ASSERT_EQ(config.allocationThresholdBytes, 1);
|
||||||
|
|
||||||
|
config.autoTune = false;
|
||||||
|
config.targetHeapBytes = 10;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
// We trigger GC on the first iteration, when the unique allocation point is faced,
|
||||||
|
// and on the last iteration when target heap size is reached.
|
||||||
|
EXPECT_CALL(scheduleGC, Call()).WillOnce([&i]() { EXPECT_THAT(i, 0); }).WillOnce([&i]() { EXPECT_THAT(i, 9); });
|
||||||
|
|
||||||
|
for (; i < 10; i++) {
|
||||||
|
threadSchedulerData.OnSafePointAllocation(1);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&scheduleGC);
|
||||||
|
}();
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "KAssert.h"
|
||||||
|
#include "Logging.hpp"
|
||||||
|
#include "Mutex.hpp"
|
||||||
|
#include "StackTrace.hpp"
|
||||||
|
#include "std_support/UnorderedSet.hpp"
|
||||||
|
|
||||||
|
namespace kotlin::gcScheduler::internal {
|
||||||
|
|
||||||
|
template <size_t SafePointStackSize = 16>
|
||||||
|
class SafePointTracker {
|
||||||
|
public:
|
||||||
|
using SafePointID = kotlin::StackTrace<SafePointStackSize>;
|
||||||
|
|
||||||
|
explicit SafePointTracker(size_t maxSize = 100000) : maxSize_(maxSize) {}
|
||||||
|
|
||||||
|
/** Returns whether the GC must be triggered on the current safe point or not. */
|
||||||
|
NO_INLINE bool registerCurrentSafePoint(size_t skipFrames) noexcept {
|
||||||
|
auto currentSP = SafePointID::current(skipFrames + 1);
|
||||||
|
|
||||||
|
std::unique_lock lock(mutex_);
|
||||||
|
|
||||||
|
// TODO: Consider replacing this naive cleaning with an LRU cache.
|
||||||
|
if (metSafePoints_.size() >= maxSize()) {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Clear safe point tracker set since it exceeded maximal size");
|
||||||
|
metSafePoints_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inserted = metSafePoints_.insert(currentSP).second;
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t maxSize() { return maxSize_; }
|
||||||
|
|
||||||
|
size_t size() { return metSafePoints_.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t maxSize_;
|
||||||
|
|
||||||
|
// TODO: Consider replacing mutex + global set with thread local sets sychronized on STW.
|
||||||
|
SpinLock<MutexThreadStateHandling::kIgnore> mutex_;
|
||||||
|
std_support::unordered_set<SafePointID> metSafePoints_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin::gcScheduler::internal
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SafePointTracker.hpp"
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
// These tests require a stack trace to contain call site addresses but
|
||||||
|
// on Windows a trace contains function addresses instead.
|
||||||
|
// So skip these tests on Windows.
|
||||||
|
#if (__MINGW32__ || __MINGW64__)
|
||||||
|
#define SKIP_ON_WINDOWS() \
|
||||||
|
do { \
|
||||||
|
GTEST_SKIP() << "Skip on Windows"; \
|
||||||
|
} while (false)
|
||||||
|
#else
|
||||||
|
#define SKIP_ON_WINDOWS() \
|
||||||
|
do { \
|
||||||
|
} while (false)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST(SafePointTrackerTest, RegisterSafePoints) {
|
||||||
|
SKIP_ON_WINDOWS();
|
||||||
|
[]() OPTNONE {
|
||||||
|
gcScheduler::internal::SafePointTracker<> tracker;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 10; i++) {
|
||||||
|
bool registered1 = tracker.registerCurrentSafePoint(0);
|
||||||
|
bool registered2 = tracker.registerCurrentSafePoint(0);
|
||||||
|
|
||||||
|
bool expected = (i == 0);
|
||||||
|
|
||||||
|
EXPECT_THAT(registered1, expected);
|
||||||
|
EXPECT_THAT(registered2, expected);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t SafePointStackSize>
|
||||||
|
OPTNONE bool registerCurrentSafePoint(gcScheduler::internal::SafePointTracker<SafePointStackSize>& tracker) {
|
||||||
|
return tracker.registerCurrentSafePoint(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SafePointTrackerTest, TrackTopFramesOnly) {
|
||||||
|
SKIP_ON_WINDOWS();
|
||||||
|
[]() OPTNONE {
|
||||||
|
gcScheduler::internal::SafePointTracker<16> longTracker;
|
||||||
|
gcScheduler::internal::SafePointTracker<1> shortTracker;
|
||||||
|
|
||||||
|
bool longRegistered1 = registerCurrentSafePoint(longTracker);
|
||||||
|
bool longRegistered2 = registerCurrentSafePoint(longTracker);
|
||||||
|
|
||||||
|
EXPECT_THAT(longRegistered1, true);
|
||||||
|
EXPECT_THAT(longRegistered2, true);
|
||||||
|
|
||||||
|
bool shortRegistered1 = registerCurrentSafePoint(shortTracker);
|
||||||
|
bool shortRegistered2 = registerCurrentSafePoint(shortTracker);
|
||||||
|
|
||||||
|
EXPECT_THAT(shortRegistered1, true);
|
||||||
|
EXPECT_THAT(shortRegistered2, false);
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SafePointTrackerTest, CleanOnSizeLimit) {
|
||||||
|
SKIP_ON_WINDOWS();
|
||||||
|
[]() OPTNONE {
|
||||||
|
gcScheduler::internal::SafePointTracker<> tracker(2);
|
||||||
|
|
||||||
|
ASSERT_THAT(tracker.size(), 0);
|
||||||
|
ASSERT_THAT(tracker.maxSize(), 2);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 3; i++) {
|
||||||
|
bool registered1 = tracker.registerCurrentSafePoint(0);
|
||||||
|
|
||||||
|
EXPECT_THAT(registered1, true);
|
||||||
|
EXPECT_THAT(tracker.size(), 1);
|
||||||
|
|
||||||
|
bool registered2 = tracker.registerCurrentSafePoint(0);
|
||||||
|
|
||||||
|
EXPECT_THAT(registered2, true);
|
||||||
|
EXPECT_THAT(tracker.size(), 2);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCScheduler.hpp"
|
||||||
+12
-61
@@ -3,50 +3,22 @@
|
|||||||
* that can be found in the LICENSE file.
|
* that can be found in the LICENSE file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef RUNTIME_GC_COMMON_GC_SCHEDULER_H
|
#pragma once
|
||||||
#define RUNTIME_GC_COMMON_GC_SCHEDULER_H
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "CompilerConstants.hpp"
|
#include "GCSchedulerConfig.hpp"
|
||||||
#include "Logging.hpp"
|
#include "KAssert.h"
|
||||||
#include "Types.h"
|
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "std_support/Memory.hpp"
|
#include "std_support/Memory.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin::gcScheduler {
|
||||||
namespace gc {
|
|
||||||
|
|
||||||
using SchedulerType = compiler::GCSchedulerType;
|
namespace test_support {
|
||||||
|
class GCSchedulerThreadDataTestApi;
|
||||||
// NOTE: When changing default values, reflect them in GC.kt as well.
|
}
|
||||||
struct GCSchedulerConfig {
|
|
||||||
// Only used when useGCTimer() is false. How many regular safepoints will trigger slow path.
|
|
||||||
std::atomic<int32_t> threshold = 100000;
|
|
||||||
// How many object bytes a thread must allocate to trigger slow path.
|
|
||||||
std::atomic<int64_t> allocationThresholdBytes = 10 * 1024;
|
|
||||||
std::atomic<bool> autoTune = true;
|
|
||||||
// The target interval between collections when Kotlin code is idle. GC will be triggered
|
|
||||||
// by timer no sooner than this value and no later than twice this value since the previous collection.
|
|
||||||
std::atomic<int64_t> regularGcIntervalMicroseconds = 10 * 1000 * 1000;
|
|
||||||
// How many object bytes must be in the heap to trigger collection. Autotunes when autoTune is true.
|
|
||||||
std::atomic<int64_t> targetHeapBytes = 1024 * 1024;
|
|
||||||
// The rate at which targetHeapBytes changes when autoTune = true. Concretely: if after the collection
|
|
||||||
// N object bytes remain in the heap, the next targetHeapBytes will be N / targetHeapUtilization capped
|
|
||||||
// between minHeapBytes and maxHeapBytes.
|
|
||||||
std::atomic<double> targetHeapUtilization = 0.5;
|
|
||||||
// The minimum value of targetHeapBytes for autoTune = true
|
|
||||||
std::atomic<int64_t> minHeapBytes = 1024 * 1024;
|
|
||||||
// The maximum value of targetHeapBytes for autoTune = true
|
|
||||||
std::atomic<int64_t> maxHeapBytes = std::numeric_limits<int64_t>::max();
|
|
||||||
|
|
||||||
std::chrono::microseconds regularGcInterval() const { return std::chrono::microseconds(regularGcIntervalMicroseconds.load()); }
|
|
||||||
};
|
|
||||||
|
|
||||||
class GCSchedulerThreadData;
|
class GCSchedulerThreadData;
|
||||||
|
|
||||||
@@ -79,17 +51,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should be called on encountering a safepoint.
|
// Should be called on encountering a safepoint.
|
||||||
void OnSafePointRegular(size_t weight) noexcept {
|
void OnSafePointRegular(size_t weight) noexcept;
|
||||||
// TODO: This is a weird design. Consider replacing switch+virtual functions with pimpl+separate compilation.
|
|
||||||
switch (compiler::getGCSchedulerType()) {
|
|
||||||
case compiler::GCSchedulerType::kOnSafepoints:
|
|
||||||
case compiler::GCSchedulerType::kAggressive:
|
|
||||||
OnSafePointRegularImpl(weight);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be called on encountering a safepoint placed by the allocator.
|
// Should be called on encountering a safepoint placed by the allocator.
|
||||||
// TODO: Should this even be a safepoint (i.e. a place, where we suspend)?
|
// TODO: Should this even be a safepoint (i.e. a place, where we suspend)?
|
||||||
@@ -108,7 +70,7 @@ public:
|
|||||||
size_t safePointsCounter() const noexcept { return safePointsCounter_; }
|
size_t safePointsCounter() const noexcept { return safePointsCounter_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class GCSchedulerThreadDataTestApi;
|
friend class test_support::GCSchedulerThreadDataTestApi;
|
||||||
|
|
||||||
void OnSafePointRegularImpl(size_t weight) noexcept {
|
void OnSafePointRegularImpl(size_t weight) noexcept {
|
||||||
safePointsCounter_ += weight;
|
safePointsCounter_ += weight;
|
||||||
@@ -142,17 +104,10 @@ private:
|
|||||||
|
|
||||||
class GCScheduler : private Pinned {
|
class GCScheduler : private Pinned {
|
||||||
public:
|
public:
|
||||||
GCScheduler() noexcept = default;
|
GCScheduler() noexcept;
|
||||||
|
|
||||||
GCSchedulerConfig& config() noexcept { return config_; }
|
GCSchedulerConfig& config() noexcept { return config_; }
|
||||||
// Only valid after `SetScheduleGC` is called.
|
GCSchedulerData& gcData() noexcept { return *gcData_; }
|
||||||
GCSchedulerData& gcData() noexcept {
|
|
||||||
RuntimeAssert(gcData_ != nullptr, "Cannot be called before SetScheduleGC");
|
|
||||||
return *gcData_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can only be called once.
|
|
||||||
void SetScheduleGC(std::function<void()> scheduleGC) noexcept;
|
|
||||||
|
|
||||||
GCSchedulerThreadData NewThreadData() noexcept {
|
GCSchedulerThreadData NewThreadData() noexcept {
|
||||||
return GCSchedulerThreadData(config_, [this](auto& threadData) { gcData_->UpdateFromThreadData(threadData); });
|
return GCSchedulerThreadData(config_, [this](auto& threadData) { gcData_->UpdateFromThreadData(threadData); });
|
||||||
@@ -161,10 +116,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
GCSchedulerConfig config_;
|
GCSchedulerConfig config_;
|
||||||
std_support::unique_ptr<GCSchedulerData> gcData_;
|
std_support::unique_ptr<GCSchedulerData> gcData_;
|
||||||
std::function<void()> scheduleGC_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gc
|
} // namespace kotlin::gcScheduler
|
||||||
} // namespace kotlin
|
|
||||||
|
|
||||||
#endif // RUNTIME_GC_COMMON_GC_SCHEDULER_H
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
namespace kotlin::gcScheduler {
|
||||||
|
|
||||||
|
// NOTE: When changing default values, reflect them in GC.kt as well.
|
||||||
|
struct GCSchedulerConfig {
|
||||||
|
// Only used when useGCTimer() is false. How many regular safepoints will trigger slow path.
|
||||||
|
std::atomic<int32_t> threshold = 100000;
|
||||||
|
// How many object bytes a thread must allocate to trigger slow path.
|
||||||
|
std::atomic<int64_t> allocationThresholdBytes = 10 * 1024;
|
||||||
|
std::atomic<bool> autoTune = true;
|
||||||
|
// The target interval between collections when Kotlin code is idle. GC will be triggered
|
||||||
|
// by timer no sooner than this value and no later than twice this value since the previous collection.
|
||||||
|
std::atomic<int64_t> regularGcIntervalMicroseconds = 10 * 1000 * 1000;
|
||||||
|
// How many object bytes must be in the heap to trigger collection. Autotunes when autoTune is true.
|
||||||
|
std::atomic<int64_t> targetHeapBytes = 1024 * 1024;
|
||||||
|
// The rate at which targetHeapBytes changes when autoTune = true. Concretely: if after the collection
|
||||||
|
// N object bytes remain in the heap, the next targetHeapBytes will be N / targetHeapUtilization capped
|
||||||
|
// between minHeapBytes and maxHeapBytes.
|
||||||
|
std::atomic<double> targetHeapUtilization = 0.5;
|
||||||
|
// The minimum value of targetHeapBytes for autoTune = true
|
||||||
|
std::atomic<int64_t> minHeapBytes = 1024 * 1024;
|
||||||
|
// The maximum value of targetHeapBytes for autoTune = true
|
||||||
|
std::atomic<int64_t> maxHeapBytes = std::numeric_limits<int64_t>::max();
|
||||||
|
|
||||||
|
std::chrono::microseconds regularGcInterval() const { return std::chrono::microseconds(regularGcIntervalMicroseconds.load()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin::gcScheduler
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCScheduler.hpp"
|
||||||
|
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "GCSchedulerTestSupport.hpp"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
using testing::_;
|
||||||
|
|
||||||
|
TEST(GCSchedulerThreadDataTest, RegularSafePoint) {
|
||||||
|
constexpr size_t kWeight = 2;
|
||||||
|
constexpr size_t kCount = 10;
|
||||||
|
constexpr size_t kThreshold = kCount * kWeight;
|
||||||
|
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.allocationThresholdBytes = 1;
|
||||||
|
config.threshold = kThreshold;
|
||||||
|
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
||||||
|
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold);
|
||||||
|
});
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), kWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GCSchedulerThreadDataTest, AllocationSafePoint) {
|
||||||
|
constexpr size_t kSize = 2;
|
||||||
|
constexpr size_t kCount = 10;
|
||||||
|
constexpr size_t kAllocationThreshold = kCount * kSize;
|
||||||
|
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.allocationThresholdBytes = kAllocationThreshold;
|
||||||
|
config.threshold = 1;
|
||||||
|
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
||||||
|
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
});
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), kSize);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GCSchedulerThreadDataTest, ResetByGC) {
|
||||||
|
constexpr size_t kWeight = 2;
|
||||||
|
constexpr size_t kSize = 2;
|
||||||
|
constexpr size_t kCount = 10;
|
||||||
|
constexpr size_t kThreshold = kCount * kWeight;
|
||||||
|
constexpr size_t kAllocationThreshold = kCount * kSize;
|
||||||
|
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.allocationThresholdBytes = kAllocationThreshold;
|
||||||
|
config.threshold = kThreshold;
|
||||||
|
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
||||||
|
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
scheduler.OnStoppedForGC();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterResetByGC) {
|
||||||
|
constexpr size_t kWeight = 2;
|
||||||
|
constexpr size_t kSize = 2;
|
||||||
|
constexpr size_t kCount = 10;
|
||||||
|
constexpr size_t kThreshold = kCount * kWeight;
|
||||||
|
constexpr size_t kAllocationThreshold = kCount * kSize;
|
||||||
|
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.allocationThresholdBytes = kAllocationThreshold;
|
||||||
|
config.threshold = kThreshold;
|
||||||
|
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
||||||
|
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
||||||
|
|
||||||
|
config.allocationThresholdBytes = kAllocationThreshold - kSize;
|
||||||
|
config.threshold = kThreshold - kWeight;
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
}
|
||||||
|
scheduler.OnStoppedForGC();
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
||||||
|
});
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
||||||
|
});
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterRegularSafePoint) {
|
||||||
|
constexpr size_t kWeight = 2;
|
||||||
|
constexpr size_t kSize = 2;
|
||||||
|
constexpr size_t kCount = 10;
|
||||||
|
constexpr size_t kThreshold = kCount * kWeight;
|
||||||
|
constexpr size_t kAllocationThreshold = kCount * kSize;
|
||||||
|
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.allocationThresholdBytes = kAllocationThreshold;
|
||||||
|
config.threshold = kThreshold;
|
||||||
|
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
||||||
|
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
||||||
|
|
||||||
|
config.allocationThresholdBytes = kAllocationThreshold - kSize;
|
||||||
|
config.threshold = kThreshold - kWeight;
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold);
|
||||||
|
});
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
||||||
|
});
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
||||||
|
});
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterAllocationSafePoint) {
|
||||||
|
constexpr size_t kWeight = 2;
|
||||||
|
constexpr size_t kSize = 2;
|
||||||
|
constexpr size_t kCount = 10;
|
||||||
|
constexpr size_t kThreshold = kCount * kWeight;
|
||||||
|
constexpr size_t kAllocationThreshold = kCount * kSize;
|
||||||
|
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
||||||
|
gcScheduler::GCSchedulerConfig config;
|
||||||
|
config.allocationThresholdBytes = kAllocationThreshold;
|
||||||
|
config.threshold = kThreshold;
|
||||||
|
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
||||||
|
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
||||||
|
|
||||||
|
config.allocationThresholdBytes = kAllocationThreshold - kSize;
|
||||||
|
config.threshold = kThreshold - kWeight;
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold);
|
||||||
|
});
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), kThreshold - kWeight);
|
||||||
|
});
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
schedulerTestApi.OnSafePointRegularImpl(kWeight);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
|
||||||
|
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
||||||
|
});
|
||||||
|
for (size_t i = 0; i < kCount - 1; ++i) {
|
||||||
|
scheduler.OnSafePointAllocation(kSize);
|
||||||
|
}
|
||||||
|
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
||||||
|
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
||||||
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCSchedulerTestSupport.hpp"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GCScheduler.hpp"
|
||||||
|
|
||||||
|
namespace kotlin::gcScheduler::test_support {
|
||||||
|
|
||||||
|
class GCSchedulerThreadDataTestApi : private Pinned {
|
||||||
|
public:
|
||||||
|
explicit GCSchedulerThreadDataTestApi(GCSchedulerThreadData& scheduler) : scheduler_(scheduler) {}
|
||||||
|
|
||||||
|
void OnSafePointRegularImpl(size_t weight) { scheduler_.OnSafePointRegularImpl(weight); }
|
||||||
|
|
||||||
|
void SetAllocatedBytes(size_t bytes) { scheduler_.allocatedBytes_ = bytes; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GCSchedulerThreadData& scheduler_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin::gcScheduler::test_support
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "GCSchedulerConfig.hpp"
|
||||||
|
|
||||||
|
namespace kotlin::gcScheduler::internal {
|
||||||
|
|
||||||
|
class HeapGrowthController {
|
||||||
|
public:
|
||||||
|
explicit HeapGrowthController(GCSchedulerConfig& config) noexcept : config_(config) {}
|
||||||
|
|
||||||
|
// Called by the mutators.
|
||||||
|
void OnAllocated(size_t allocatedBytes) noexcept { allocatedBytes_ += allocatedBytes; }
|
||||||
|
|
||||||
|
// Called by the GC thread.
|
||||||
|
void OnPerformFullGC() noexcept { allocatedBytes_ = 0; }
|
||||||
|
|
||||||
|
// Called by the GC thread.
|
||||||
|
void UpdateAliveSetBytes(size_t bytes) noexcept {
|
||||||
|
lastAliveSetBytes_ = bytes;
|
||||||
|
|
||||||
|
if (config_.autoTune.load()) {
|
||||||
|
double targetHeapBytes = static_cast<double>(bytes) / config_.targetHeapUtilization;
|
||||||
|
if (!std::isfinite(targetHeapBytes)) {
|
||||||
|
// This shouldn't happen in practice: targetHeapUtilization is in (0, 1]. But in case it does, don't touch anything.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
double minHeapBytes = static_cast<double>(config_.minHeapBytes.load());
|
||||||
|
double maxHeapBytes = static_cast<double>(config_.maxHeapBytes.load());
|
||||||
|
targetHeapBytes = std::min(std::max(targetHeapBytes, minHeapBytes), maxHeapBytes);
|
||||||
|
config_.targetHeapBytes = static_cast<int64_t>(targetHeapBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by the mutators.
|
||||||
|
bool NeedsGC() const noexcept {
|
||||||
|
uint64_t currentHeapBytes = allocatedBytes_.load() + lastAliveSetBytes_.load();
|
||||||
|
uint64_t targetHeapBytes = config_.targetHeapBytes;
|
||||||
|
return currentHeapBytes >= targetHeapBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GCSchedulerConfig& config_;
|
||||||
|
// Updated by both the mutators and the GC thread.
|
||||||
|
std::atomic<size_t> allocatedBytes_ = 0;
|
||||||
|
// Updated by the GC thread, read by the mutators.
|
||||||
|
std::atomic<size_t> lastAliveSetBytes_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin::gcScheduler::internal
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "GCSchedulerConfig.hpp"
|
||||||
|
|
||||||
|
namespace kotlin::gcScheduler::internal {
|
||||||
|
|
||||||
|
template <typename Clock>
|
||||||
|
class RegularIntervalPacer {
|
||||||
|
public:
|
||||||
|
using TimePoint = std::chrono::time_point<Clock>;
|
||||||
|
|
||||||
|
explicit RegularIntervalPacer(GCSchedulerConfig& config) noexcept : config_(config), lastGC_(Clock::now()) {}
|
||||||
|
|
||||||
|
// Called by the mutators or the timer thread.
|
||||||
|
bool NeedsGC() const noexcept {
|
||||||
|
auto currentTime = Clock::now();
|
||||||
|
return currentTime >= lastGC_.load() + config_.regularGcInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by the GC thread.
|
||||||
|
void OnPerformFullGC() noexcept { lastGC_ = Clock::now(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GCSchedulerConfig& config_;
|
||||||
|
// Updated by the GC thread, read by the mutators or the timer thread.
|
||||||
|
std::atomic<TimePoint> lastGC_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin::gcScheduler::internal
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GCSchedulerImpl.hpp"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
void gcScheduler::GCSchedulerThreadData::OnSafePointRegular(size_t weight) noexcept {}
|
||||||
|
|
||||||
|
gcScheduler::GCScheduler::GCScheduler() noexcept : gcData_(std_support::make_unique<internal::GCSchedulerDataManual>()) {}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GCScheduler.hpp"
|
||||||
|
|
||||||
|
#include "Logging.hpp"
|
||||||
|
|
||||||
|
namespace kotlin::gcScheduler::internal {
|
||||||
|
|
||||||
|
class GCSchedulerDataManual : public GCSchedulerData {
|
||||||
|
public:
|
||||||
|
GCSchedulerDataManual() noexcept { RuntimeLogInfo({kTagGC}, "Manual GC scheduler initialized"); }
|
||||||
|
|
||||||
|
void UpdateFromThreadData(GCSchedulerThreadData& threadData) noexcept override {}
|
||||||
|
void OnPerformFullGC() noexcept override {}
|
||||||
|
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kotlin::gcScheduler::internal
|
||||||
@@ -37,7 +37,6 @@ extern "C" const int32_t Kotlin_needDebugInfo;
|
|||||||
extern "C" const int32_t Kotlin_runtimeAssertsMode;
|
extern "C" const int32_t Kotlin_runtimeAssertsMode;
|
||||||
extern "C" const int32_t Kotlin_disableMmap;
|
extern "C" const int32_t Kotlin_disableMmap;
|
||||||
extern "C" const char* const Kotlin_runtimeLogs;
|
extern "C" const char* const Kotlin_runtimeLogs;
|
||||||
extern "C" const int32_t Kotlin_gcSchedulerType;
|
|
||||||
extern "C" const int32_t Kotlin_freezingEnabled;
|
extern "C" const int32_t Kotlin_freezingEnabled;
|
||||||
extern "C" const int32_t Kotlin_freezingChecksEnabled;
|
extern "C" const int32_t Kotlin_freezingChecksEnabled;
|
||||||
|
|
||||||
@@ -65,14 +64,6 @@ enum class WorkerExceptionHandling : int32_t {
|
|||||||
kUseHook = 1,
|
kUseHook = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Must match GCSchedulerType in GCSchedulerType.kt
|
|
||||||
enum class GCSchedulerType {
|
|
||||||
kDisabled = 0,
|
|
||||||
kWithTimer = 1,
|
|
||||||
kOnSafepoints = 2,
|
|
||||||
kAggressive = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Must match AppStateTracking in AppStateTracking.kt
|
// Must match AppStateTracking in AppStateTracking.kt
|
||||||
enum class AppStateTracking {
|
enum class AppStateTracking {
|
||||||
kDisabled = 0,
|
kDisabled = 0,
|
||||||
@@ -107,11 +98,6 @@ ALWAYS_INLINE inline bool freezingChecksEnabled() noexcept {
|
|||||||
return Kotlin_freezingChecksEnabled != 0;
|
return Kotlin_freezingChecksEnabled != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE inline GCSchedulerType getGCSchedulerType() noexcept {
|
|
||||||
return static_cast<compiler::GCSchedulerType>(Kotlin_gcSchedulerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
WorkerExceptionHandling workerExceptionHandling() noexcept;
|
WorkerExceptionHandling workerExceptionHandling() noexcept;
|
||||||
DestroyRuntimeMode destroyRuntimeMode() noexcept;
|
DestroyRuntimeMode destroyRuntimeMode() noexcept;
|
||||||
bool gcMarkSingleThreaded() noexcept;
|
bool gcMarkSingleThreaded() noexcept;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public:
|
|||||||
GlobalsRegistry& globalsRegistry() noexcept { return globalsRegistry_; }
|
GlobalsRegistry& globalsRegistry() noexcept { return globalsRegistry_; }
|
||||||
SpecialRefRegistry& specialRefRegistry() noexcept { return specialRefRegistry_; }
|
SpecialRefRegistry& specialRefRegistry() noexcept { return specialRefRegistry_; }
|
||||||
ExtraObjectDataFactory& extraObjectDataFactory() noexcept { return extraObjectDataFactory_; }
|
ExtraObjectDataFactory& extraObjectDataFactory() noexcept { return extraObjectDataFactory_; }
|
||||||
|
gcScheduler::GCScheduler& gcScheduler() noexcept { return gcScheduler_; }
|
||||||
gc::GC& gc() noexcept { return gc_; }
|
gc::GC& gc() noexcept { return gc_; }
|
||||||
AppStateTracking& appStateTracking() noexcept { return appStateTracking_; }
|
AppStateTracking& appStateTracking() noexcept { return appStateTracking_; }
|
||||||
|
|
||||||
@@ -43,7 +44,8 @@ private:
|
|||||||
GlobalsRegistry globalsRegistry_;
|
GlobalsRegistry globalsRegistry_;
|
||||||
SpecialRefRegistry specialRefRegistry_;
|
SpecialRefRegistry specialRefRegistry_;
|
||||||
ExtraObjectDataFactory extraObjectDataFactory_;
|
ExtraObjectDataFactory extraObjectDataFactory_;
|
||||||
gc::GC gc_;
|
gcScheduler::GCScheduler gcScheduler_;
|
||||||
|
gc::GC gc_{gcScheduler_};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mm
|
} // namespace mm
|
||||||
|
|||||||
@@ -343,11 +343,11 @@ extern "C" void Kotlin_native_internal_GC_start(ObjHeader*) {
|
|||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setThreshold(ObjHeader*, KInt value) {
|
extern "C" void Kotlin_native_internal_GC_setThreshold(ObjHeader*, KInt value) {
|
||||||
RuntimeAssert(value > 0, "Must be handled by the caller");
|
RuntimeAssert(value > 0, "Must be handled by the caller");
|
||||||
mm::GlobalData::Instance().gc().gcSchedulerConfig().threshold = value;
|
mm::GlobalData::Instance().gcScheduler().config().threshold = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" KInt Kotlin_native_internal_GC_getThreshold(ObjHeader*) {
|
extern "C" KInt Kotlin_native_internal_GC_getThreshold(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gc().gcSchedulerConfig().threshold.load();
|
return mm::GlobalData::Instance().gcScheduler().config().threshold.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setCollectCyclesThreshold(ObjHeader*, int64_t value) {
|
extern "C" void Kotlin_native_internal_GC_setCollectCyclesThreshold(ObjHeader*, int64_t value) {
|
||||||
@@ -363,64 +363,64 @@ extern "C" int64_t Kotlin_native_internal_GC_getCollectCyclesThreshold(ObjHeader
|
|||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setThresholdAllocations(ObjHeader*, int64_t value) {
|
extern "C" void Kotlin_native_internal_GC_setThresholdAllocations(ObjHeader*, int64_t value) {
|
||||||
RuntimeAssert(value > 0, "Must be handled by the caller");
|
RuntimeAssert(value > 0, "Must be handled by the caller");
|
||||||
mm::GlobalData::Instance().gc().gcSchedulerConfig().allocationThresholdBytes = value;
|
mm::GlobalData::Instance().gcScheduler().config().allocationThresholdBytes = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" int64_t Kotlin_native_internal_GC_getThresholdAllocations(ObjHeader*) {
|
extern "C" int64_t Kotlin_native_internal_GC_getThresholdAllocations(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gc().gcSchedulerConfig().allocationThresholdBytes.load();
|
return mm::GlobalData::Instance().gcScheduler().config().allocationThresholdBytes.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setTuneThreshold(ObjHeader*, KBoolean value) {
|
extern "C" void Kotlin_native_internal_GC_setTuneThreshold(ObjHeader*, KBoolean value) {
|
||||||
mm::GlobalData::Instance().gc().gcSchedulerConfig().autoTune = value;
|
mm::GlobalData::Instance().gcScheduler().config().autoTune = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" KBoolean Kotlin_native_internal_GC_getTuneThreshold(ObjHeader*) {
|
extern "C" KBoolean Kotlin_native_internal_GC_getTuneThreshold(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gc().gcSchedulerConfig().autoTune.load();
|
return mm::GlobalData::Instance().gcScheduler().config().autoTune.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" KLong Kotlin_native_internal_GC_getRegularGCIntervalMicroseconds(ObjHeader*) {
|
extern "C" KLong Kotlin_native_internal_GC_getRegularGCIntervalMicroseconds(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gc().gcSchedulerConfig().regularGcIntervalMicroseconds.load();
|
return mm::GlobalData::Instance().gcScheduler().config().regularGcIntervalMicroseconds.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setRegularGCIntervalMicroseconds(ObjHeader*, KLong value) {
|
extern "C" void Kotlin_native_internal_GC_setRegularGCIntervalMicroseconds(ObjHeader*, KLong value) {
|
||||||
RuntimeAssert(value >= 0, "Must be handled by the caller");
|
RuntimeAssert(value >= 0, "Must be handled by the caller");
|
||||||
mm::GlobalData::Instance().gc().gcSchedulerConfig().regularGcIntervalMicroseconds = value;
|
mm::GlobalData::Instance().gcScheduler().config().regularGcIntervalMicroseconds = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" KLong Kotlin_native_internal_GC_getTargetHeapBytes(ObjHeader*) {
|
extern "C" KLong Kotlin_native_internal_GC_getTargetHeapBytes(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gc().gcSchedulerConfig().targetHeapBytes.load();
|
return mm::GlobalData::Instance().gcScheduler().config().targetHeapBytes.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setTargetHeapBytes(ObjHeader*, KLong value) {
|
extern "C" void Kotlin_native_internal_GC_setTargetHeapBytes(ObjHeader*, KLong value) {
|
||||||
RuntimeAssert(value >= 0, "Must be handled by the caller");
|
RuntimeAssert(value >= 0, "Must be handled by the caller");
|
||||||
mm::GlobalData::Instance().gc().gcSchedulerConfig().targetHeapBytes = value;
|
mm::GlobalData::Instance().gcScheduler().config().targetHeapBytes = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" KDouble Kotlin_native_internal_GC_getTargetHeapUtilization(ObjHeader*) {
|
extern "C" KDouble Kotlin_native_internal_GC_getTargetHeapUtilization(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gc().gcSchedulerConfig().targetHeapUtilization.load();
|
return mm::GlobalData::Instance().gcScheduler().config().targetHeapUtilization.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setTargetHeapUtilization(ObjHeader*, KDouble value) {
|
extern "C" void Kotlin_native_internal_GC_setTargetHeapUtilization(ObjHeader*, KDouble value) {
|
||||||
RuntimeAssert(value > 0 && value <= 1, "Must be handled by the caller");
|
RuntimeAssert(value > 0 && value <= 1, "Must be handled by the caller");
|
||||||
mm::GlobalData::Instance().gc().gcSchedulerConfig().targetHeapUtilization = value;
|
mm::GlobalData::Instance().gcScheduler().config().targetHeapUtilization = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" KLong Kotlin_native_internal_GC_getMaxHeapBytes(ObjHeader*) {
|
extern "C" KLong Kotlin_native_internal_GC_getMaxHeapBytes(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gc().gcSchedulerConfig().maxHeapBytes.load();
|
return mm::GlobalData::Instance().gcScheduler().config().maxHeapBytes.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setMaxHeapBytes(ObjHeader*, KLong value) {
|
extern "C" void Kotlin_native_internal_GC_setMaxHeapBytes(ObjHeader*, KLong value) {
|
||||||
RuntimeAssert(value >= 0, "Must be handled by the caller");
|
RuntimeAssert(value >= 0, "Must be handled by the caller");
|
||||||
mm::GlobalData::Instance().gc().gcSchedulerConfig().maxHeapBytes = value;
|
mm::GlobalData::Instance().gcScheduler().config().maxHeapBytes = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" KLong Kotlin_native_internal_GC_getMinHeapBytes(ObjHeader*) {
|
extern "C" KLong Kotlin_native_internal_GC_getMinHeapBytes(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gc().gcSchedulerConfig().minHeapBytes.load();
|
return mm::GlobalData::Instance().gcScheduler().config().minHeapBytes.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setMinHeapBytes(ObjHeader*, KLong value) {
|
extern "C" void Kotlin_native_internal_GC_setMinHeapBytes(ObjHeader*, KLong value) {
|
||||||
RuntimeAssert(value >= 0, "Must be handled by the caller");
|
RuntimeAssert(value >= 0, "Must be handled by the caller");
|
||||||
mm::GlobalData::Instance().gc().gcSchedulerConfig().minHeapBytes = value;
|
mm::GlobalData::Instance().gcScheduler().config().minHeapBytes = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" OBJ_GETTER(Kotlin_native_internal_GC_detectCycles, ObjHeader*) {
|
extern "C" OBJ_GETTER(Kotlin_native_internal_GC_detectCycles, ObjHeader*) {
|
||||||
@@ -543,12 +543,14 @@ extern "C" void CheckGlobalsAccessible() {
|
|||||||
extern "C" RUNTIME_NOTHROW NO_INLINE void Kotlin_mm_safePointFunctionPrologue() {
|
extern "C" RUNTIME_NOTHROW NO_INLINE void Kotlin_mm_safePointFunctionPrologue() {
|
||||||
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
|
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
|
||||||
AssertThreadState(threadData, ThreadState::kRunnable);
|
AssertThreadState(threadData, ThreadState::kRunnable);
|
||||||
|
threadData->gcScheduler().OnSafePointRegular(gcScheduler::GCSchedulerThreadData::kFunctionPrologueWeight);
|
||||||
threadData->gc().SafePointFunctionPrologue();
|
threadData->gc().SafePointFunctionPrologue();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" RUNTIME_NOTHROW CODEGEN_INLINE_POLICY void Kotlin_mm_safePointWhileLoopBody() {
|
extern "C" RUNTIME_NOTHROW CODEGEN_INLINE_POLICY void Kotlin_mm_safePointWhileLoopBody() {
|
||||||
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
|
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
|
||||||
AssertThreadState(threadData, ThreadState::kRunnable);
|
AssertThreadState(threadData, ThreadState::kRunnable);
|
||||||
|
threadData->gcScheduler().OnSafePointRegular(gcScheduler::GCSchedulerThreadData::kLoopBodyWeight);
|
||||||
threadData->gc().SafePointLoopBody();
|
threadData->gc().SafePointLoopBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ public:
|
|||||||
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
||||||
specialRefRegistry_(SpecialRefRegistry::instance()),
|
specialRefRegistry_(SpecialRefRegistry::instance()),
|
||||||
extraObjectDataThreadQueue_(ExtraObjectDataFactory::Instance()),
|
extraObjectDataThreadQueue_(ExtraObjectDataFactory::Instance()),
|
||||||
gc_(GlobalData::Instance().gc(), *this),
|
gcScheduler_(GlobalData::Instance().gcScheduler().NewThreadData()),
|
||||||
|
gc_(GlobalData::Instance().gc(), gcScheduler_, *this),
|
||||||
suspensionData_(ThreadState::kNative, *this) {}
|
suspensionData_(ThreadState::kNative, *this) {}
|
||||||
|
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
@@ -58,6 +59,8 @@ public:
|
|||||||
|
|
||||||
std_support::vector<std::pair<ObjHeader**, ObjHeader*>>& initializingSingletons() noexcept { return initializingSingletons_; }
|
std_support::vector<std::pair<ObjHeader**, ObjHeader*>>& initializingSingletons() noexcept { return initializingSingletons_; }
|
||||||
|
|
||||||
|
gcScheduler::GCSchedulerThreadData& gcScheduler() noexcept { return gcScheduler_; }
|
||||||
|
|
||||||
gc::GC::ThreadData& gc() noexcept { return gc_; }
|
gc::GC::ThreadData& gc() noexcept { return gc_; }
|
||||||
|
|
||||||
ThreadSuspensionData& suspensionData() { return suspensionData_; }
|
ThreadSuspensionData& suspensionData() { return suspensionData_; }
|
||||||
@@ -84,6 +87,7 @@ private:
|
|||||||
SpecialRefRegistry::ThreadQueue specialRefRegistry_;
|
SpecialRefRegistry::ThreadQueue specialRefRegistry_;
|
||||||
ExtraObjectDataFactory::ThreadQueue extraObjectDataThreadQueue_;
|
ExtraObjectDataFactory::ThreadQueue extraObjectDataThreadQueue_;
|
||||||
ShadowStack shadowStack_;
|
ShadowStack shadowStack_;
|
||||||
|
gcScheduler::GCSchedulerThreadData gcScheduler_;
|
||||||
gc::GC::ThreadData gc_;
|
gc::GC::ThreadData gc_;
|
||||||
std_support::vector<std::pair<ObjHeader**, ObjHeader*>> initializingSingletons_;
|
std_support::vector<std::pair<ObjHeader**, ObjHeader*>> initializingSingletons_;
|
||||||
ThreadSuspensionData suspensionData_;
|
ThreadSuspensionData suspensionData_;
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ extern const int32_t Kotlin_disableMmap = 1;
|
|||||||
extern const int32_t Kotlin_disableMmap = 0;
|
extern const int32_t Kotlin_disableMmap = 0;
|
||||||
#endif
|
#endif
|
||||||
extern const char* const Kotlin_runtimeLogs = nullptr;
|
extern const char* const Kotlin_runtimeLogs = nullptr;
|
||||||
extern const int32_t Kotlin_gcSchedulerType = static_cast<int32_t>(kotlin::compiler::GCSchedulerType::kDisabled);
|
|
||||||
extern const int32_t Kotlin_freezingChecksEnabled = 1;
|
extern const int32_t Kotlin_freezingChecksEnabled = 1;
|
||||||
extern const int32_t Kotlin_freezingEnabled = 1;
|
extern const int32_t Kotlin_freezingEnabled = 1;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user