// Copyright 2012 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/heap-controller.h"
|
#include "src/isolate-inl.h"
|
|
namespace v8 {
|
namespace internal {
|
|
// Given GC speed in bytes per ms, the allocation throughput in bytes per ms
|
// (mutator speed), this function returns the heap growing factor that will
|
// achieve the kTargetMutatorUtilisation if the GC speed and the mutator speed
|
// remain the same until the next GC.
|
//
|
// For a fixed time-frame T = TM + TG, the mutator utilization is the ratio
|
// TM / (TM + TG), where TM is the time spent in the mutator and TG is the
|
// time spent in the garbage collector.
|
//
|
// Let MU be kTargetMutatorUtilisation, the desired mutator utilization for the
|
// time-frame from the end of the current GC to the end of the next GC. Based
|
// on the MU we can compute the heap growing factor F as
|
//
|
// F = R * (1 - MU) / (R * (1 - MU) - MU), where R = gc_speed / mutator_speed.
|
//
|
// This formula can be derived as follows.
|
//
|
// F = Limit / Live by definition, where the Limit is the allocation limit,
|
// and the Live is size of live objects.
|
// Let’s assume that we already know the Limit. Then:
|
// TG = Limit / gc_speed
|
// TM = (TM + TG) * MU, by definition of MU.
|
// TM = TG * MU / (1 - MU)
|
// TM = Limit * MU / (gc_speed * (1 - MU))
|
// On the other hand, if the allocation throughput remains constant:
|
// Limit = Live + TM * allocation_throughput = Live + TM * mutator_speed
|
// Solving it for TM, we get
|
// TM = (Limit - Live) / mutator_speed
|
// Combining the two equation for TM:
|
// (Limit - Live) / mutator_speed = Limit * MU / (gc_speed * (1 - MU))
|
// (Limit - Live) = Limit * MU * mutator_speed / (gc_speed * (1 - MU))
|
// substitute R = gc_speed / mutator_speed
|
// (Limit - Live) = Limit * MU / (R * (1 - MU))
|
// substitute F = Limit / Live
|
// F - 1 = F * MU / (R * (1 - MU))
|
// F - F * MU / (R * (1 - MU)) = 1
|
// F * (1 - MU / (R * (1 - MU))) = 1
|
// F * (R * (1 - MU) - MU) / (R * (1 - MU)) = 1
|
// F = R * (1 - MU) / (R * (1 - MU) - MU)
|
double MemoryController::GrowingFactor(double gc_speed, double mutator_speed,
|
double max_factor) {
|
DCHECK_LE(kMinGrowingFactor, max_factor);
|
DCHECK_GE(kMaxGrowingFactor, max_factor);
|
if (gc_speed == 0 || mutator_speed == 0) return max_factor;
|
|
const double speed_ratio = gc_speed / mutator_speed;
|
|
const double a = speed_ratio * (1 - kTargetMutatorUtilization);
|
const double b =
|
speed_ratio * (1 - kTargetMutatorUtilization) - kTargetMutatorUtilization;
|
|
// The factor is a / b, but we need to check for small b first.
|
double factor = (a < b * max_factor) ? a / b : max_factor;
|
factor = Min(factor, max_factor);
|
factor = Max(factor, kMinGrowingFactor);
|
return factor;
|
}
|
|
double MemoryController::MaxGrowingFactor(size_t curr_max_size) {
|
const double min_small_factor = 1.3;
|
const double max_small_factor = 2.0;
|
const double high_factor = 4.0;
|
|
size_t max_size_in_mb = curr_max_size / MB;
|
max_size_in_mb = Max(max_size_in_mb, kMinSize);
|
|
// If we are on a device with lots of memory, we allow a high heap
|
// growing factor.
|
if (max_size_in_mb >= kMaxSize) {
|
return high_factor;
|
}
|
|
DCHECK_GE(max_size_in_mb, kMinSize);
|
DCHECK_LT(max_size_in_mb, kMaxSize);
|
|
// On smaller devices we linearly scale the factor: (X-A)/(B-A)*(D-C)+C
|
double factor = (max_size_in_mb - kMinSize) *
|
(max_small_factor - min_small_factor) /
|
(kMaxSize - kMinSize) +
|
min_small_factor;
|
return factor;
|
}
|
|
size_t MemoryController::CalculateAllocationLimit(
|
size_t curr_size, size_t max_size, double gc_speed, double mutator_speed,
|
size_t new_space_capacity, Heap::HeapGrowingMode growing_mode) {
|
double max_factor = MaxGrowingFactor(max_size);
|
double factor = GrowingFactor(gc_speed, mutator_speed, max_factor);
|
|
if (FLAG_trace_gc_verbose) {
|
heap_->isolate()->PrintWithTimestamp(
|
"%s factor %.1f based on mu=%.3f, speed_ratio=%.f "
|
"(gc=%.f, mutator=%.f)\n",
|
ControllerName(), factor, kTargetMutatorUtilization,
|
gc_speed / mutator_speed, gc_speed, mutator_speed);
|
}
|
|
if (growing_mode == Heap::HeapGrowingMode::kConservative ||
|
growing_mode == Heap::HeapGrowingMode::kSlow) {
|
factor = Min(factor, kConservativeGrowingFactor);
|
}
|
|
if (growing_mode == Heap::HeapGrowingMode::kMinimal) {
|
factor = kMinGrowingFactor;
|
}
|
|
if (FLAG_heap_growing_percent > 0) {
|
factor = 1.0 + FLAG_heap_growing_percent / 100.0;
|
}
|
|
CHECK_LT(1.0, factor);
|
CHECK_LT(0, curr_size);
|
uint64_t limit = static_cast<uint64_t>(curr_size * factor);
|
limit = Max(limit, static_cast<uint64_t>(curr_size) +
|
MinimumAllocationLimitGrowingStep(growing_mode));
|
limit += new_space_capacity;
|
uint64_t halfway_to_the_max =
|
(static_cast<uint64_t>(curr_size) + max_size) / 2;
|
size_t result = static_cast<size_t>(Min(limit, halfway_to_the_max));
|
|
if (FLAG_trace_gc_verbose) {
|
heap_->isolate()->PrintWithTimestamp(
|
"%s Limit: old size: %" PRIuS " KB, new limit: %" PRIuS " KB (%.1f)\n",
|
ControllerName(), curr_size / KB, result / KB, factor);
|
}
|
|
return result;
|
}
|
|
size_t MemoryController::MinimumAllocationLimitGrowingStep(
|
Heap::HeapGrowingMode growing_mode) {
|
const size_t kRegularAllocationLimitGrowingStep = 8;
|
const size_t kLowMemoryAllocationLimitGrowingStep = 2;
|
size_t limit = (Page::kPageSize > MB ? Page::kPageSize : MB);
|
return limit * (growing_mode == Heap::HeapGrowingMode::kConservative
|
? kLowMemoryAllocationLimitGrowingStep
|
: kRegularAllocationLimitGrowingStep);
|
}
|
|
} // namespace internal
|
} // namespace v8
|