blob: 45c606505fd878a4b3d2119e273626f3735f6dbe [file] [log] [blame]
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/cppgc/heap-growing.h"
#include <cmath>
#include <memory>
#include "include/cppgc/platform.h"
#include "src/base/macros.h"
#include "src/heap/cppgc/globals.h"
#include "src/heap/cppgc/heap.h"
#include "src/heap/cppgc/incremental-marking-schedule.h"
#include "src/heap/cppgc/stats-collector.h"
#include "src/heap/cppgc/task-handle.h"
namespace cppgc {
namespace internal {
namespace {
// Minimum ratio between limit for incremental GC and limit for atomic GC
// (to guarantee that limits are not to close to each other).
constexpr double kMaximumLimitRatioForIncrementalGC = 0.9;
// Minimum ratio between limit for incremental GC and limit for atomic GC
// (to guarantee that limit is not too close to current allocated size).
constexpr double kMinimumLimitRatioForIncrementalGC = 0.5;
} // namespace
class HeapGrowing::HeapGrowingImpl final
: public StatsCollector::AllocationObserver {
public:
HeapGrowingImpl(GarbageCollector*, StatsCollector*,
cppgc::Heap::ResourceConstraints);
~HeapGrowingImpl();
HeapGrowingImpl(const HeapGrowingImpl&) = delete;
HeapGrowingImpl& operator=(const HeapGrowingImpl&) = delete;
void AllocatedObjectSizeIncreased(size_t) final;
// Only trigger GC on growing.
void AllocatedObjectSizeDecreased(size_t) final {}
void ResetAllocatedObjectSize(size_t) final;
size_t limit_for_atomic_gc() const { return limit_for_atomic_gc_; }
size_t limit_for_incremental_gc() const { return limit_for_incremental_gc_; }
void DisableForTesting();
private:
void ConfigureLimit(size_t allocated_object_size);
GarbageCollector* collector_;
StatsCollector* stats_collector_;
// Allow 1 MB heap by default;
size_t initial_heap_size_ = 1 * kMB;
size_t limit_for_atomic_gc_ = 0; // See ConfigureLimit().
size_t limit_for_incremental_gc_ = 0; // See ConfigureLimit().
SingleThreadedHandle gc_task_handle_;
bool disabled_for_testing_ = false;
};
HeapGrowing::HeapGrowingImpl::HeapGrowingImpl(
GarbageCollector* collector, StatsCollector* stats_collector,
cppgc::Heap::ResourceConstraints constraints)
: collector_(collector),
stats_collector_(stats_collector),
gc_task_handle_(SingleThreadedHandle::NonEmptyTag{}) {
if (constraints.initial_heap_size_bytes > 0) {
initial_heap_size_ = constraints.initial_heap_size_bytes;
}
constexpr size_t kNoAllocatedBytes = 0;
ConfigureLimit(kNoAllocatedBytes);
stats_collector->RegisterObserver(this);
}
HeapGrowing::HeapGrowingImpl::~HeapGrowingImpl() {
stats_collector_->UnregisterObserver(this);
}
void HeapGrowing::HeapGrowingImpl::AllocatedObjectSizeIncreased(size_t) {
if (disabled_for_testing_) return;
size_t allocated_object_size = stats_collector_->allocated_object_size();
if (allocated_object_size > limit_for_atomic_gc_) {
collector_->CollectGarbage(
GarbageCollector::Config::ConservativeAtomicConfig());
} else if (allocated_object_size > limit_for_incremental_gc_) {
collector_->StartIncrementalGarbageCollection(
GarbageCollector::Config::ConservativeIncrementalConfig());
}
}
void HeapGrowing::HeapGrowingImpl::ResetAllocatedObjectSize(
size_t allocated_object_size) {
ConfigureLimit(allocated_object_size);
}
void HeapGrowing::HeapGrowingImpl::ConfigureLimit(
size_t allocated_object_size) {
const size_t size = std::max(allocated_object_size, initial_heap_size_);
limit_for_atomic_gc_ = std::max(static_cast<size_t>(size * kGrowingFactor),
size + kMinLimitIncrease);
// Estimate when to start incremental GC based on current allocation speed.
// Ideally we start incremental GC such that it is ready to finalize no
// later than when we reach |limit_for_atomic_gc_|. However, we need to cap
// |limit_for_incremental_gc_| within a range to prevent:
// 1) |limit_for_incremental_gc_| being too close to |limit_for_atomic_gc_|
// such that incremental gc gets nothing done before reaching
// |limit_for_atomic_gc_| (in case where the allocation rate is very low).
// 2) |limit_for_incremental_gc_| being too close to |size| such that GC is
// essentially always running and write barriers are always active (in
// case allocation rate is very high).
size_t estimated_bytes_allocated_during_incremental_gc =
std::ceil(IncrementalMarkingSchedule::kEstimatedMarkingTimeMs *
stats_collector_->GetRecentAllocationSpeedInBytesPerMs());
size_t limit_incremental_gc_based_on_allocation_rate =
limit_for_atomic_gc_ - estimated_bytes_allocated_during_incremental_gc;
size_t maximum_limit_incremental_gc =
size + (limit_for_atomic_gc_ - size) * kMaximumLimitRatioForIncrementalGC;
size_t minimum_limit_incremental_gc =
size + (limit_for_atomic_gc_ - size) * kMinimumLimitRatioForIncrementalGC;
limit_for_incremental_gc_ =
std::max(minimum_limit_incremental_gc,
std::min(maximum_limit_incremental_gc,
limit_incremental_gc_based_on_allocation_rate));
}
void HeapGrowing::HeapGrowingImpl::DisableForTesting() {
disabled_for_testing_ = true;
}
HeapGrowing::HeapGrowing(GarbageCollector* collector,
StatsCollector* stats_collector,
cppgc::Heap::ResourceConstraints constraints)
: impl_(std::make_unique<HeapGrowing::HeapGrowingImpl>(
collector, stats_collector, constraints)) {}
HeapGrowing::~HeapGrowing() = default;
size_t HeapGrowing::limit_for_atomic_gc() const {
return impl_->limit_for_atomic_gc();
}
size_t HeapGrowing::limit_for_incremental_gc() const {
return impl_->limit_for_incremental_gc();
}
void HeapGrowing::DisableForTesting() { impl_->DisableForTesting(); }
// static
constexpr double HeapGrowing::kGrowingFactor;
} // namespace internal
} // namespace cppgc