[K/N] Make GC scheduler parametrized by Clock. ^KT-48537
This commit is contained in:
committed by
Space
parent
e014e6b154
commit
53de23200d
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user