[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 <cmath>
|
||||||
|
|
||||||
#include "CompilerConstants.hpp"
|
#include "CompilerConstants.hpp"
|
||||||
|
#include "GCSchedulerImpl.hpp"
|
||||||
#include "GlobalData.hpp"
|
#include "GlobalData.hpp"
|
||||||
#include "KAssert.h"
|
#include "KAssert.h"
|
||||||
#include "Porting.h"
|
#include "Porting.h"
|
||||||
#include "ThreadRegistry.hpp"
|
#include "ThreadRegistry.hpp"
|
||||||
#include "ThreadData.hpp"
|
#include "ThreadData.hpp"
|
||||||
|
|
||||||
#ifndef KONAN_NO_THREADS
|
|
||||||
#include "RepeatedTimer.hpp"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class HeapGrowthController {
|
KStdUniquePtr<gc::GCSchedulerData> MakeGCSchedulerData(
|
||||||
public:
|
gc::SchedulerType type, gc::GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept {
|
||||||
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 {
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SchedulerType::kDisabled:
|
case gc::SchedulerType::kDisabled:
|
||||||
RuntimeLogDebug({kTagGC}, "GC scheduler disabled");
|
RuntimeLogDebug({kTagGC}, "GC scheduler disabled");
|
||||||
return ::make_unique<GCEmptySchedulerData>();
|
return ::make_unique<gc::internal::GCEmptySchedulerData>();
|
||||||
case SchedulerType::kWithTimer:
|
case gc::SchedulerType::kWithTimer:
|
||||||
#ifndef KONAN_NO_THREADS
|
#ifndef KONAN_NO_THREADS
|
||||||
RuntimeLogDebug({kTagGC}, "Initializing timer-based GC scheduler");
|
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
|
#else
|
||||||
RuntimeFail("GC scheduler with timer is not supported on this platform");
|
RuntimeFail("GC scheduler with timer is not supported on this platform");
|
||||||
#endif
|
#endif
|
||||||
case SchedulerType::kOnSafepoints:
|
case gc::SchedulerType::kOnSafepoints:
|
||||||
RuntimeLogDebug({kTagGC}, "Initializing safe-point-based GC scheduler");
|
RuntimeLogDebug({kTagGC}, "Initializing safe-point-based GC scheduler");
|
||||||
return ::make_unique<GCSchedulerDataOnSafepoints>(config, std::move(scheduleGC), std::move(currentTimeProvider));
|
return ::make_unique<gc::internal::GCSchedulerDataOnSafepoints<steady_clock>>(config, std::move(scheduleGC));
|
||||||
case SchedulerType::kAggressive:
|
case gc::SchedulerType::kAggressive:
|
||||||
RuntimeLogDebug({kTagGC}, "Initializing aggressive GC scheduler");
|
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 {
|
void gc::GCScheduler::SetScheduleGC(std::function<void()> scheduleGC) noexcept {
|
||||||
RuntimeAssert(static_cast<bool>(scheduleGC), "scheduleGC cannot be empty");
|
RuntimeAssert(static_cast<bool>(scheduleGC), "scheduleGC cannot be empty");
|
||||||
RuntimeAssert(!static_cast<bool>(scheduleGC_), "scheduleGC must not have been set");
|
RuntimeAssert(!static_cast<bool>(scheduleGC_), "scheduleGC must not have been set");
|
||||||
scheduleGC_ = std::move(scheduleGC);
|
scheduleGC_ = std::move(scheduleGC);
|
||||||
RuntimeAssert(gcData_ == nullptr, "gcData_ must not be set prior to scheduleGC call");
|
RuntimeAssert(gcData_ == nullptr, "gcData_ must not be set prior to scheduleGC call");
|
||||||
gcData_ = internal::MakeGCSchedulerData(
|
gcData_ = MakeGCSchedulerData(compiler::getGCSchedulerType(), config_, scheduleGC_);
|
||||||
compiler::getGCSchedulerType(), config_, scheduleGC_, []() { return std::chrono::steady_clock::now(); });
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,16 +163,6 @@ private:
|
|||||||
std::function<void()> scheduleGC_;
|
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 gc
|
||||||
} // namespace kotlin
|
} // 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 "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "ClockTestSupport.hpp"
|
||||||
|
#include "GCSchedulerImpl.hpp"
|
||||||
#include "SingleThreadExecutor.hpp"
|
#include "SingleThreadExecutor.hpp"
|
||||||
#include "TestSupport.hpp"
|
#include "TestSupport.hpp"
|
||||||
|
|
||||||
@@ -281,8 +283,6 @@ TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterAllocationSafePoint) {
|
|||||||
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
EXPECT_THAT(scheduler.safePointsCounter(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
using TimePoint = std::chrono::time_point<std::chrono::steady_clock>;
|
|
||||||
|
|
||||||
class MutatorThread : private Pinned {
|
class MutatorThread : private Pinned {
|
||||||
public:
|
public:
|
||||||
MutatorThread(GCSchedulerConfig& config, std::function<void(GCSchedulerThreadData&)> slowPath) :
|
MutatorThread(GCSchedulerConfig& config, std::function<void(GCSchedulerThreadData&)> slowPath) :
|
||||||
@@ -309,60 +309,62 @@ private:
|
|||||||
SingleThreadExecutor<Context> executor_;
|
SingleThreadExecutor<Context> executor_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <compiler::GCSchedulerType schedulerType, int MutatorCount>
|
template <typename GCScheduler, int MutatorCount>
|
||||||
class GCSchedulerDataTestApi {
|
class GCSchedulerDataTestApi {
|
||||||
public:
|
public:
|
||||||
static constexpr TimePoint initialTime = TimePoint();
|
explicit GCSchedulerDataTestApi(GCSchedulerConfig& config) : scheduler_(config, scheduleGC_.AsStdFunction()) {
|
||||||
|
|
||||||
explicit GCSchedulerDataTestApi(GCSchedulerConfig& config) {
|
|
||||||
ON_CALL(currentTime_, Call()).WillByDefault([&]() { return time_.load(); });
|
|
||||||
|
|
||||||
scheduler_ = internal::MakeGCSchedulerData(schedulerType, config, scheduleGC_.AsStdFunction(), currentTime_.AsStdFunction());
|
|
||||||
|
|
||||||
mutators_.reserve(MutatorCount);
|
mutators_.reserve(MutatorCount);
|
||||||
for (int i = 0; i < MutatorCount; ++i) {
|
for (int i = 0; i < MutatorCount; ++i) {
|
||||||
mutators_.emplace_back(make_unique<MutatorThread>(
|
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); }
|
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_; }
|
testing::MockFunction<void()>& scheduleGC() { return scheduleGC_; }
|
||||||
|
|
||||||
template <typename Duration>
|
template <typename Duration>
|
||||||
void advance_time(Duration duration) {
|
void advance_time(Duration duration) {
|
||||||
auto time = time_.load();
|
test_support::manual_clock::sleep_for(duration);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::atomic<TimePoint> time_ = initialTime;
|
|
||||||
KStdVector<KStdUniquePtr<MutatorThread>> mutators_;
|
KStdVector<KStdUniquePtr<MutatorThread>> mutators_;
|
||||||
testing::MockFunction<void()> scheduleGC_;
|
testing::MockFunction<void()> scheduleGC_;
|
||||||
testing::NiceMock<testing::MockFunction<TimePoint()>> currentTime_;
|
GCScheduler scheduler_;
|
||||||
KStdUniquePtr<GCSchedulerData> 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;
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = (mutatorsCount + 1) * 10;
|
config.targetHeapBytes = (mutatorsCount + 1) * 10;
|
||||||
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
|
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
||||||
KStdVector<std::future<void>> futures;
|
KStdVector<std::future<void>> futures;
|
||||||
@@ -392,17 +394,17 @@ TEST(GCSchedulerDataOnSafePoints, CollectOnTargetHeapReached) {
|
|||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GCSchedulerDataOnSafePoints, CollectOnTimeoutReached) {
|
TEST_F(GCSchedulerDataOnSafePointsTest, CollectOnTimeoutReached) {
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(20)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = std::numeric_limits<size_t>::max();
|
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);
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
||||||
schedulerTestApi.advance_time(std::chrono::milliseconds(10));
|
schedulerTestApi.advance_time(microseconds(5));
|
||||||
KStdVector<std::future<void>> futures;
|
KStdVector<std::future<void>> futures;
|
||||||
for (int i = 0; i < mutatorsCount; ++i) {
|
for (int i = 0; i < mutatorsCount; ++i) {
|
||||||
futures.push_back(schedulerTestApi.Allocate(i, 0));
|
futures.push_back(schedulerTestApi.Allocate(i, 0));
|
||||||
@@ -413,52 +415,52 @@ TEST(GCSchedulerDataOnSafePoints, CollectOnTimeoutReached) {
|
|||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
schedulerTestApi.advance_time(std::chrono::milliseconds(15));
|
schedulerTestApi.advance_time(microseconds(5));
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
schedulerTestApi.Allocate(0, 0).get();
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
schedulerTestApi.OnPerformFullGC();
|
schedulerTestApi.OnPerformFullGC();
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GCSchedulerDataOnSafePoints, FullTimeoutAfterLastGC) {
|
TEST_F(GCSchedulerDataOnSafePointsTest, FullTimeoutAfterLastGC) {
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(20)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = 10;
|
config.targetHeapBytes = 10;
|
||||||
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
|
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
schedulerTestApi.advance_time(std::chrono::milliseconds(10));
|
schedulerTestApi.advance_time(microseconds(5));
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
schedulerTestApi.OnPerformFullGC();
|
schedulerTestApi.OnPerformFullGC();
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(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();
|
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());
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
schedulerTestApi.advance_time(std::chrono::milliseconds(10));
|
schedulerTestApi.advance_time(microseconds(5));
|
||||||
schedulerTestApi.Allocate(0, 0).get();
|
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());
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
schedulerTestApi.OnPerformFullGC();
|
schedulerTestApi.OnPerformFullGC();
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GCSchedulerDataOnSafePoints, DoNotTuneTargetHeap) {
|
TEST_F(GCSchedulerDataOnSafePointsTest, DoNotTuneTargetHeap) {
|
||||||
constexpr int mutatorsCount = 1;
|
constexpr int mutatorsCount = 1;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = 10;
|
config.targetHeapBytes = 10;
|
||||||
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
|
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
@@ -469,17 +471,17 @@ TEST(GCSchedulerDataOnSafePoints, DoNotTuneTargetHeap) {
|
|||||||
EXPECT_THAT(config.targetHeapBytes.load(), 10);
|
EXPECT_THAT(config.targetHeapBytes.load(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GCSchedulerDataOnSafePoints, TuneTargetHeap) {
|
TEST_F(GCSchedulerDataOnSafePointsTest, TuneTargetHeap) {
|
||||||
constexpr int mutatorsCount = 1;
|
constexpr int mutatorsCount = 1;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = true;
|
config.autoTune = true;
|
||||||
config.targetHeapBytes = 10;
|
config.targetHeapBytes = 10;
|
||||||
config.targetHeapUtilization = 0.5;
|
config.targetHeapUtilization = 0.5;
|
||||||
config.minHeapBytes = 5;
|
config.minHeapBytes = 5;
|
||||||
config.maxHeapBytes = 50;
|
config.maxHeapBytes = 50;
|
||||||
GCSchedulerDataTestApi<compiler::GCSchedulerType::kOnSafepoints, mutatorsCount> schedulerTestApi(config);
|
GCSchedulerDataOnSafepointsTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
@@ -538,14 +540,14 @@ TEST(GCSchedulerDataOnSafePoints, TuneTargetHeap) {
|
|||||||
EXPECT_THAT(config.targetHeapBytes.load(), 5);
|
EXPECT_THAT(config.targetHeapBytes.load(), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GCSchedulerDataWithTimer, CollectOnTargetHeapReached) {
|
TEST_F(GCSchedulerDataWithTimerTest, CollectOnTargetHeapReached) {
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = (mutatorsCount + 1) * 10;
|
config.targetHeapBytes = (mutatorsCount + 1) * 10;
|
||||||
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
|
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
||||||
KStdVector<std::future<void>> futures;
|
KStdVector<std::future<void>> futures;
|
||||||
@@ -575,72 +577,59 @@ TEST(GCSchedulerDataWithTimer, CollectOnTargetHeapReached) {
|
|||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GCSchedulerDataWithTimer, CollectOnTimeoutReached) {
|
TEST_F(GCSchedulerDataWithTimerTest, CollectOnTimeoutReached) {
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(2000)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = std::numeric_limits<size_t>::max();
|
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);
|
// Wait until the timer is initialized.
|
||||||
schedulerTestApi.advance_time(std::chrono::milliseconds(1000));
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
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());
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
schedulerTestApi.OnPerformFullGC();
|
schedulerTestApi.OnPerformFullGC();
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GCSchedulerDataWithTimer, FullTimeoutAfterLastGC) {
|
TEST_F(GCSchedulerDataWithTimerTest, FullTimeoutAfterLastGC) {
|
||||||
constexpr int mutatorsCount = kDefaultThreadCount;
|
constexpr int mutatorsCount = kDefaultThreadCount;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(2000)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = 10;
|
config.targetHeapBytes = 10;
|
||||||
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
|
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
// Wait until the timer is initialized.
|
||||||
schedulerTestApi.advance_time(std::chrono::milliseconds(1000));
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
|
||||||
|
|
||||||
|
schedulerTestApi.advance_time(microseconds(5));
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
||||||
schedulerTestApi.OnPerformFullGC();
|
schedulerTestApi.OnPerformFullGC();
|
||||||
schedulerTestApi.UpdateAliveSetBytes(0);
|
schedulerTestApi.UpdateAliveSetBytes(0);
|
||||||
|
|
||||||
|
// pending should restart to be 10us since the previous collection without scheduling another GC.
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0);
|
||||||
schedulerTestApi.advance_time(std::chrono::milliseconds(1500));
|
test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10));
|
||||||
// It's now 250 ms since the start, but only 150ms since previous collection.
|
|
||||||
// However, the timer has fired once ~50ms ago.
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC());
|
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;
|
constexpr int mutatorsCount = 1;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = 10;
|
config.targetHeapBytes = 10;
|
||||||
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
|
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
@@ -651,17 +640,17 @@ TEST(GCSchedulerDataWithTimer, DoNotTuneTargetHeap) {
|
|||||||
EXPECT_THAT(config.targetHeapBytes.load(), 10);
|
EXPECT_THAT(config.targetHeapBytes.load(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GCSchedulerDataWithTimer, TuneTargetHeap) {
|
TEST_F(GCSchedulerDataWithTimerTest, TuneTargetHeap) {
|
||||||
constexpr int mutatorsCount = 1;
|
constexpr int mutatorsCount = 1;
|
||||||
|
|
||||||
GCSchedulerConfig config;
|
GCSchedulerConfig config;
|
||||||
config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count();
|
config.regularGcIntervalMicroseconds = 10;
|
||||||
config.autoTune = true;
|
config.autoTune = true;
|
||||||
config.targetHeapBytes = 10;
|
config.targetHeapBytes = 10;
|
||||||
config.targetHeapUtilization = 0.5;
|
config.targetHeapUtilization = 0.5;
|
||||||
config.minHeapBytes = 5;
|
config.minHeapBytes = 5;
|
||||||
config.maxHeapBytes = 50;
|
config.maxHeapBytes = 50;
|
||||||
GCSchedulerDataTestApi<compiler::GCSchedulerType::kWithTimer, mutatorsCount> schedulerTestApi(config);
|
GCSchedulerDataWithTimerTestApi<mutatorsCount> schedulerTestApi(config);
|
||||||
|
|
||||||
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
EXPECT_CALL(schedulerTestApi.scheduleGC(), Call());
|
||||||
schedulerTestApi.Allocate(0, 10).get();
|
schedulerTestApi.Allocate(0, 10).get();
|
||||||
|
|||||||
Reference in New Issue
Block a user