[K/N] Make GC scheduler parametrized by Clock. ^KT-48537

This commit is contained in:
Alexander Shabalin
2022-02-18 17:52:28 +03:00
committed by Space
parent e014e6b154
commit 53de23200d
4 changed files with 262 additions and 278 deletions
@@ -8,215 +8,45 @@
#include <cmath>
#include "CompilerConstants.hpp"
#include "GCSchedulerImpl.hpp"
#include "GlobalData.hpp"
#include "KAssert.h"
#include "Porting.h"
#include "ThreadRegistry.hpp"
#include "ThreadData.hpp"
#ifndef KONAN_NO_THREADS
#include "RepeatedTimer.hpp"
#endif
using namespace kotlin;
namespace {
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;
};
class RegularIntervalPacer {
public:
using TimePoint = std::chrono::time_point<std::chrono::steady_clock>;
using CurrentTimeProvider = std::function<TimePoint()>;
RegularIntervalPacer(gc::GCSchedulerConfig& config, CurrentTimeProvider currentTimeProvider) noexcept :
config_(config), currentTimeProvider_(currentTimeProvider), lastGC_(currentTimeProvider_()) {}
// Called by the mutators or the timer thread.
bool NeedsGC() const noexcept {
auto currentTimeProvider = currentTimeProvider_();
return currentTimeProvider >= lastGC_.load() + config_.regularGcInterval();
}
// Called by the GC thread.
void OnPerformFullGC() noexcept { lastGC_ = currentTimeProvider_(); }
private:
gc::GCSchedulerConfig& config_;
CurrentTimeProvider currentTimeProvider_;
// Updated by the GC thread, read by the mutators or the timer thread.
std::atomic<TimePoint> lastGC_;
};
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
class GCSchedulerDataWithTimer : public gc::GCSchedulerData {
public:
GCSchedulerDataWithTimer(
gc::GCSchedulerConfig& config,
std::function<void()> scheduleGC,
RegularIntervalPacer::CurrentTimeProvider currentTimeProvider) noexcept :
config_(config),
heapGrowthController_(config),
regularIntervalPacer_(config, currentTimeProvider),
scheduleGC_(std::move(scheduleGC)),
timer_("GC Timer thread", config_.regularGcInterval(), [this] {
if (regularIntervalPacer_.NeedsGC()) {
scheduleGC_();
}
}) {}
void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override {
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
if (heapGrowthController_.NeedsGC()) {
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_;
HeapGrowthController heapGrowthController_;
RegularIntervalPacer regularIntervalPacer_;
std::function<void()> scheduleGC_;
RepeatedTimer<> timer_;
};
#endif // !KONAN_NO_THREADS
class GCSchedulerDataOnSafepoints : public gc::GCSchedulerData {
public:
GCSchedulerDataOnSafepoints(
gc::GCSchedulerConfig& config,
std::function<void()> scheduleGC,
RegularIntervalPacer::CurrentTimeProvider currentTimeProvider) noexcept :
heapGrowthController_(config), regularIntervalPacer_(config, currentTimeProvider), 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 regularIntervalPacer_;
std::function<void()> scheduleGC_;
};
class GCSchedulerDataAggressive : public gc::GCSchedulerData {
public:
GCSchedulerDataAggressive(gc::GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
scheduleGC_(std::move(scheduleGC)) {
// TODO: Make it even more aggressive and run on a subset of backend.native tests.
config.threshold = 1000;
config.allocationThresholdBytes = 10000;
}
void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override { scheduleGC_(); }
void OnPerformFullGC() noexcept override {}
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
private:
std::function<void()> scheduleGC_;
};
} // namespace
KStdUniquePtr<gc::GCSchedulerData> kotlin::gc::internal::MakeGCSchedulerData(
SchedulerType type,
gc::GCSchedulerConfig& config,
std::function<void()> scheduleGC,
std::function<std::chrono::time_point<std::chrono::steady_clock>()> currentTimeProvider) noexcept {
KStdUniquePtr<gc::GCSchedulerData> MakeGCSchedulerData(
gc::SchedulerType type, gc::GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept {
switch (type) {
case SchedulerType::kDisabled:
case gc::SchedulerType::kDisabled:
RuntimeLogDebug({kTagGC}, "GC scheduler disabled");
return ::make_unique<GCEmptySchedulerData>();
case SchedulerType::kWithTimer:
return ::make_unique<gc::internal::GCEmptySchedulerData>();
case gc::SchedulerType::kWithTimer:
#ifndef KONAN_NO_THREADS
RuntimeLogDebug({kTagGC}, "Initializing timer-based GC scheduler");
return ::make_unique<GCSchedulerDataWithTimer>(config, std::move(scheduleGC), std::move(currentTimeProvider));
return ::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 SchedulerType::kOnSafepoints:
case gc::SchedulerType::kOnSafepoints:
RuntimeLogDebug({kTagGC}, "Initializing safe-point-based GC scheduler");
return ::make_unique<GCSchedulerDataOnSafepoints>(config, std::move(scheduleGC), std::move(currentTimeProvider));
case SchedulerType::kAggressive:
return ::make_unique<gc::internal::GCSchedulerDataOnSafepoints<steady_clock>>(config, std::move(scheduleGC));
case gc::SchedulerType::kAggressive:
RuntimeLogDebug({kTagGC}, "Initializing aggressive GC scheduler");
return ::make_unique<GCSchedulerDataAggressive>(config, std::move(scheduleGC));
return ::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_ = internal::MakeGCSchedulerData(
compiler::getGCSchedulerType(), config_, scheduleGC_, []() { return std::chrono::steady_clock::now(); });
gcData_ = MakeGCSchedulerData(compiler::getGCSchedulerType(), config_, scheduleGC_);
}
@@ -163,16 +163,6 @@ private:
std::function<void()> scheduleGC_;
};
namespace internal {
KStdUniquePtr<gc::GCSchedulerData> MakeGCSchedulerData(
SchedulerType type,
GCSchedulerConfig& config,
std::function<void()> scheduleGC,
std::function<std::chrono::time_point<std::chrono::steady_clock>()> currentTime) noexcept;
}
} // namespace gc
} // namespace kotlin
@@ -0,0 +1,175 @@
/*
* 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 "Clock.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_;
};
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),
heapGrowthController_(config),
regularIntervalPacer_(config),
scheduleGC_(std::move(scheduleGC)),
timer_("GC Timer thread", config_.regularGcInterval(), [this] {
if (regularIntervalPacer_.NeedsGC()) {
scheduleGC_();
}
}) {}
void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override {
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
if (heapGrowthController_.NeedsGC()) {
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_;
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)) {
// TODO: Make it even more aggressive and run on a subset of backend.native tests.
config.threshold = 1000;
config.allocationThresholdBytes = 10000;
}
void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override { scheduleGC_(); }
void OnPerformFullGC() noexcept override {}
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
private:
std::function<void()> scheduleGC_;
};
} // namespace kotlin::gc::internal
@@ -11,6 +11,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "ClockTestSupport.hpp"
#include "GCSchedulerImpl.hpp"
#include "SingleThreadExecutor.hpp"
#include "TestSupport.hpp"
@@ -281,8 +283,6 @@ TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterAllocationSafePoint) {
EXPECT_THAT(scheduler.safePointsCounter(), 0);
}
using TimePoint = std::chrono::time_point<std::chrono::steady_clock>;
class MutatorThread : private Pinned {
public:
MutatorThread(GCSchedulerConfig& config, std::function<void(GCSchedulerThreadData&)> slowPath) :
@@ -309,60 +309,62 @@ private:
SingleThreadExecutor<Context> executor_;
};
template <compiler::GCSchedulerType schedulerType, int MutatorCount>
template <typename GCScheduler, int MutatorCount>
class GCSchedulerDataTestApi {
public:
static constexpr TimePoint initialTime = TimePoint();
explicit GCSchedulerDataTestApi(GCSchedulerConfig& config) {
ON_CALL(currentTime_, Call()).WillByDefault([&]() { return time_.load(); });
scheduler_ = internal::MakeGCSchedulerData(schedulerType, config, scheduleGC_.AsStdFunction(), currentTime_.AsStdFunction());
explicit GCSchedulerDataTestApi(GCSchedulerConfig& config) : scheduler_(config, scheduleGC_.AsStdFunction()) {
mutators_.reserve(MutatorCount);
for (int i = 0; i < MutatorCount; ++i) {
mutators_.emplace_back(make_unique<MutatorThread>(
config, [this](GCSchedulerThreadData& threadData) { scheduler_->UpdateFromThreadData(threadData); }));
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 OnPerformFullGC() { scheduler_.OnPerformFullGC(); }
void UpdateAliveSetBytes(size_t bytes) { scheduler_->UpdateAliveSetBytes(bytes); }
void UpdateAliveSetBytes(size_t bytes) { scheduler_.UpdateAliveSetBytes(bytes); }
testing::MockFunction<void()>& scheduleGC() { return scheduleGC_; }
template <typename Duration>
void advance_time(Duration duration) {
auto time = time_.load();
while (true) {
auto newTime = time + std::chrono::duration_cast<TimePoint::duration>(duration);
if (time_.compare_exchange_weak(time, newTime)) {
// TODO: Figure out mocking out RepeatedTimer (or clock underneath it) to avoid sleeping.
std::this_thread::sleep_for(duration);
return;
}
}
test_support::manual_clock::sleep_for(duration);
}
private:
std::atomic<TimePoint> time_ = initialTime;
KStdVector<KStdUniquePtr<MutatorThread>> mutators_;
testing::MockFunction<void()> scheduleGC_;
testing::NiceMock<testing::MockFunction<TimePoint()>> currentTime_;
KStdUniquePtr<GCSchedulerData> scheduler_;
GCScheduler scheduler_;
};
TEST(GCSchedulerDataOnSafePoints, CollectOnTargetHeapReached) {
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 = std::chrono::microseconds(std::chrono::minutes(10)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = (mutatorsCount + 1) * 10;
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
KStdVector<std::future<void>> futures;
@@ -392,17 +394,17 @@ TEST(GCSchedulerDataOnSafePoints, CollectOnTargetHeapReached) {
schedulerTestApi.UpdateAliveSetBytes(0);
}
TEST(GCSchedulerDataOnSafePoints, CollectOnTimeoutReached) {
TEST_F(GCSchedulerDataOnSafePointsTest, CollectOnTimeoutReached) {
constexpr int mutatorsCount = kDefaultThreadCount;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(20)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = std::numeric_limits<size_t>::max();
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
schedulerTestApi.advance_time(std::chrono::milliseconds(10));
schedulerTestApi.advance_time(microseconds(5));
KStdVector<std::future<void>> futures;
for (int i = 0; i < mutatorsCount; ++i) {
futures.push_back(schedulerTestApi.Allocate(i, 0));
@@ -413,52 +415,52 @@ TEST(GCSchedulerDataOnSafePoints, CollectOnTimeoutReached) {
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
schedulerTestApi.advance_time(std::chrono::milliseconds(15));
schedulerTestApi.advance_time(microseconds(5));
schedulerTestApi.Allocate(0, 0).get();
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
schedulerTestApi.OnPerformFullGC();
schedulerTestApi.UpdateAliveSetBytes(0);
}
TEST(GCSchedulerDataOnSafePoints, FullTimeoutAfterLastGC) {
TEST_F(GCSchedulerDataOnSafePointsTest, FullTimeoutAfterLastGC) {
constexpr int mutatorsCount = kDefaultThreadCount;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(20)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = 10;
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
schedulerTestApi.advance_time(std::chrono::milliseconds(10));
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(std::chrono::milliseconds(15));
schedulerTestApi.advance_time(microseconds(5));
schedulerTestApi.Allocate(0, 0).get();
// It's now 25 ms since the start, but only 15ms since previous collection.
// 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(std::chrono::milliseconds(10));
schedulerTestApi.advance_time(microseconds(5));
schedulerTestApi.Allocate(0, 0).get();
// It's now 25 ms since the previous collection.
// It's now 10 us since the previous collection.
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
schedulerTestApi.OnPerformFullGC();
schedulerTestApi.UpdateAliveSetBytes(0);
}
TEST(GCSchedulerDataOnSafePoints, DoNotTuneTargetHeap) {
TEST_F(GCSchedulerDataOnSafePointsTest, DoNotTuneTargetHeap) {
constexpr int mutatorsCount = 1;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = 10;
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
schedulerTestApi.Allocate(0, 10).get();
@@ -469,17 +471,17 @@ TEST(GCSchedulerDataOnSafePoints, DoNotTuneTargetHeap) {
EXPECT_THAT(config.targetHeapBytes.load(), 10);
}
TEST(GCSchedulerDataOnSafePoints, TuneTargetHeap) {
TEST_F(GCSchedulerDataOnSafePointsTest, TuneTargetHeap) {
constexpr int mutatorsCount = 1;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = true;
config.targetHeapBytes = 10;
config.targetHeapUtilization = 0.5;
config.minHeapBytes = 5;
config.maxHeapBytes = 50;
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
schedulerTestApi.Allocate(0, 10).get();
@@ -538,14 +540,14 @@ TEST(GCSchedulerDataOnSafePoints, TuneTargetHeap) {
EXPECT_THAT(config.targetHeapBytes.load(), 5);
}
TEST(GCSchedulerDataWithTimer, CollectOnTargetHeapReached) {
TEST_F(GCSchedulerDataWithTimerTest, CollectOnTargetHeapReached) {
constexpr int mutatorsCount = kDefaultThreadCount;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = (mutatorsCount + 1) * 10;
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
KStdVector<std::future<void>> futures;
@@ -575,72 +577,59 @@ TEST(GCSchedulerDataWithTimer, CollectOnTargetHeapReached) {
schedulerTestApi.UpdateAliveSetBytes(0);
}
TEST(GCSchedulerDataWithTimer, CollectOnTimeoutReached) {
TEST_F(GCSchedulerDataWithTimerTest, CollectOnTimeoutReached) {
constexpr int mutatorsCount = kDefaultThreadCount;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(2000)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = std::numeric_limits<size_t>::max();
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
schedulerTestApi.advance_time(std::chrono::milliseconds(1000));
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
// 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(std::chrono::milliseconds(1500));
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(GCSchedulerDataWithTimer, FullTimeoutAfterLastGC) {
TEST_F(GCSchedulerDataWithTimerTest, FullTimeoutAfterLastGC) {
constexpr int mutatorsCount = kDefaultThreadCount;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(2000)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = 10;
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
schedulerTestApi.advance_time(std::chrono::milliseconds(1000));
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
// 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);
schedulerTestApi.advance_time(std::chrono::milliseconds(1500));
// It's now 250 ms since the start, but only 150ms since previous collection.
// However, the timer has fired once ~50ms ago.
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
schedulerTestApi.advance_time(std::chrono::milliseconds(1000));
// It's now 250 ms since the previous collection, but the timer will fire in ~50ms.
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
schedulerTestApi.advance_time(std::chrono::milliseconds(1000));
// 350ms since previous collection, and the timer has fired ~50ms ago.
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
schedulerTestApi.OnPerformFullGC();
schedulerTestApi.UpdateAliveSetBytes(0);
}
TEST(GCSchedulerDataWithTimer, DoNotTuneTargetHeap) {
TEST_F(GCSchedulerDataWithTimerTest, DoNotTuneTargetHeap) {
constexpr int mutatorsCount = 1;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = false;
config.targetHeapBytes = 10;
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
schedulerTestApi.Allocate(0, 10).get();
@@ -651,17 +640,17 @@ TEST(GCSchedulerDataWithTimer, DoNotTuneTargetHeap) {
EXPECT_THAT(config.targetHeapBytes.load(), 10);
}
TEST(GCSchedulerDataWithTimer, TuneTargetHeap) {
TEST_F(GCSchedulerDataWithTimerTest, TuneTargetHeap) {
constexpr int mutatorsCount = 1;
GCSchedulerConfig config;
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
config.regularGcIntervalMicroseconds = 10;
config.autoTune = true;
config.targetHeapBytes = 10;
config.targetHeapUtilization = 0.5;
config.minHeapBytes = 5;
config.maxHeapBytes = 50;
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
schedulerTestApi.Allocate(0, 10).get();