Compute the heap growing factor based on mutator utilization and allocation throughput.

Doc: https://goo.gl/LLGvBs

BUG=

Review URL: https://codereview.chromium.org/1163143009

Cr-Commit-Position: refs/heads/master@{#29015}
This commit is contained in:
ulan 2015-06-15 01:32:52 -07:00 committed by Commit bot
parent 29715d7c07
commit 143a9e0431
6 changed files with 156 additions and 46 deletions

View File

@ -618,12 +618,12 @@ intptr_t GCTracer::FinalIncrementalMarkCompactSpeedInBytesPerMillisecond()
double GCTracer::CombinedMarkCompactSpeedInBytesPerMillisecond() {
if (combined_mark_compact_speed_cache_ > 0)
return combined_mark_compact_speed_cache_;
const double kEpsilon = 1;
const double kMinimumMarkingSpeed = 0.5;
double speed1 =
static_cast<double>(IncrementalMarkingSpeedInBytesPerMillisecond());
double speed2 = static_cast<double>(
FinalIncrementalMarkCompactSpeedInBytesPerMillisecond());
if (speed1 + speed2 < kEpsilon) {
if (speed1 < kMinimumMarkingSpeed || speed2 < kMinimumMarkingSpeed) {
// No data for the incremental marking speed.
// Return the non-incremental mark-compact speed.
combined_mark_compact_speed_cache_ =
@ -684,9 +684,11 @@ size_t GCTracer::AllocationThroughputInBytesPerMillisecond(
}
size_t GCTracer::CurrentAllocationThroughputInBytesPerMillisecond() const {
size_t GCTracer::CurrentOldGenerationAllocationThroughputInBytesPerMillisecond()
const {
static const double kThroughputTimeFrame = 5000;
return AllocationThroughputInBytesPerMillisecond(kThroughputTimeFrame);
return OldGenerationAllocationThroughputInBytesPerMillisecond(
kThroughputTimeFrame);
}

View File

@ -402,10 +402,10 @@ class GCTracer {
// Returns 0 if no allocation events have been recorded.
size_t AllocationThroughputInBytesPerMillisecond(double time_ms) const;
// Allocation throughput in heap in bytes/milliseconds in
// Allocation throughput in old generation in bytes/milliseconds in
// the last five seconds.
// Returns 0 if no allocation events have been recorded.
size_t CurrentAllocationThroughputInBytesPerMillisecond() const;
size_t CurrentOldGenerationAllocationThroughputInBytesPerMillisecond() const;
// Computes the context disposal rate in milliseconds. It takes the time
// frame of the first recorded context disposal to the current time and

View File

@ -1242,7 +1242,9 @@ bool Heap::PerformGarbageCollection(
old_gen_exhausted_ = false;
old_generation_size_configured_ = true;
// This should be updated before PostGarbageCollectionProcessing, which can
// cause another GC.
// cause another GC. Take into account the objects promoted during GC.
old_generation_allocation_counter_ +=
static_cast<size_t>(promoted_objects_size_);
old_generation_size_at_last_gc_ = PromotedSpaceSizeOfObjects();
} else {
Scavenge();
@ -1285,9 +1287,12 @@ bool Heap::PerformGarbageCollection(
// Register the amount of external allocated memory.
amount_of_external_allocated_memory_at_last_global_gc_ =
amount_of_external_allocated_memory_;
SetOldGenerationAllocationLimit(
PromotedSpaceSizeOfObjects(),
tracer()->CurrentAllocationThroughputInBytesPerMillisecond());
double gc_speed = tracer()->CombinedMarkCompactSpeedInBytesPerMillisecond();
double mutator_speed = static_cast<double>(
tracer()
->CurrentOldGenerationAllocationThroughputInBytesPerMillisecond());
intptr_t old_gen_size = PromotedSpaceSizeOfObjects();
SetOldGenerationAllocationLimit(old_gen_size, gc_speed, mutator_speed);
}
{
@ -5423,6 +5428,70 @@ int64_t Heap::PromotedExternalMemorySize() {
}
const double Heap::kMinHeapGrowingFactor = 1.1;
const double Heap::kMaxHeapGrowingFactor = 4.0;
const double Heap::kMaxHeapGrowingFactorMemoryConstrained = 2.0;
const double Heap::kMaxHeapGrowingFactorIdle = 1.5;
const double Heap::kTargetMutatorUtilization = 0.97;
// 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.
// Lets 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 Heap::HeapGrowingFactor(double gc_speed, double mutator_speed) {
if (gc_speed == 0 || mutator_speed == 0) return kMaxHeapGrowingFactor;
const double speed_ratio = gc_speed / mutator_speed;
const double mu = kTargetMutatorUtilization;
const double a = speed_ratio * (1 - mu);
const double b = speed_ratio * (1 - mu) - mu;
// The factor is a / b, but we need to check for small b first.
double factor =
(a < b * kMaxHeapGrowingFactor) ? a / b : kMaxHeapGrowingFactor;
factor = Min(factor, kMaxHeapGrowingFactor);
factor = Max(factor, kMinHeapGrowingFactor);
return factor;
}
intptr_t Heap::CalculateOldGenerationAllocationLimit(double factor,
intptr_t old_gen_size) {
CHECK(factor > 1.0);
@ -5435,52 +5504,34 @@ intptr_t Heap::CalculateOldGenerationAllocationLimit(double factor,
}
void Heap::SetOldGenerationAllocationLimit(
intptr_t old_gen_size, size_t current_allocation_throughput) {
// Allocation throughput on Android devices is typically lower than on
// non-mobile devices.
#if V8_OS_ANDROID
const size_t kHighThroughput = 2500;
const size_t kLowThroughput = 250;
#else
const size_t kHighThroughput = 10000;
const size_t kLowThroughput = 1000;
#endif
const double min_scaling_factor = 1.1;
const double max_scaling_factor = 1.5;
double max_factor = 4;
const double idle_max_factor = 1.5;
void Heap::SetOldGenerationAllocationLimit(intptr_t old_gen_size,
double gc_speed,
double mutator_speed) {
double factor = HeapGrowingFactor(gc_speed, mutator_speed);
if (FLAG_trace_gc_verbose) {
PrintIsolate(isolate_,
"Heap growing factor %.1f based on mu=%.3f, speed_ratio=%.f "
"(gc=%.f, mutator=%.f)\n",
factor, kTargetMutatorUtilization, gc_speed / mutator_speed,
gc_speed, mutator_speed);
}
// We set the old generation growing factor to 2 to grow the heap slower on
// memory-constrained devices.
if (max_old_generation_size_ <= kMaxOldSpaceSizeMediumMemoryDevice) {
max_factor = 2;
}
double factor;
double idle_factor;
if (current_allocation_throughput == 0 ||
current_allocation_throughput >= kHighThroughput) {
factor = max_factor;
} else if (current_allocation_throughput <= kLowThroughput) {
factor = min_scaling_factor;
} else {
// Compute factor using linear interpolation between points
// (kHighThroughput, max_scaling_factor) and (kLowThroughput, min_factor).
factor = min_scaling_factor +
(current_allocation_throughput - kLowThroughput) *
(max_scaling_factor - min_scaling_factor) /
(kHighThroughput - kLowThroughput);
factor = Min(factor, kMaxHeapGrowingFactorMemoryConstrained);
}
if (FLAG_stress_compaction ||
mark_compact_collector()->reduce_memory_footprint_) {
factor = min_scaling_factor;
factor = kMinHeapGrowingFactor;
}
// TODO(hpayer): Investigate if idle_old_generation_allocation_limit_ is still
// needed after taking the allocation rate for the old generation limit into
// account.
idle_factor = Min(factor, idle_max_factor);
double idle_factor = Min(factor, kMaxHeapGrowingFactorIdle);
old_generation_allocation_limit_ =
CalculateOldGenerationAllocationLimit(factor, old_gen_size);

View File

@ -1157,14 +1157,22 @@ class Heap {
static const int kTraceRingBufferSize = 512;
static const int kStacktraceBufferSize = 512;
static const double kMinHeapGrowingFactor;
static const double kMaxHeapGrowingFactor;
static const double kMaxHeapGrowingFactorMemoryConstrained;
static const double kMaxHeapGrowingFactorIdle;
static const double kTargetMutatorUtilization;
static double HeapGrowingFactor(double gc_speed, double mutator_speed);
// Calculates the allocation limit based on a given growing factor and a
// given old generation size.
intptr_t CalculateOldGenerationAllocationLimit(double factor,
intptr_t old_gen_size);
// Sets the allocation limit to trigger the next full garbage collection.
void SetOldGenerationAllocationLimit(intptr_t old_gen_size,
size_t current_allocation_throughput);
void SetOldGenerationAllocationLimit(intptr_t old_gen_size, double gc_speed,
double mutator_speed);
// Indicates whether inline bump-pointer allocation has been disabled.
bool inline_allocation_disabled() { return inline_allocation_disabled_; }

View File

@ -0,0 +1,48 @@
// Copyright 2014 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 <cmath>
#include <limits>
#include "src/objects.h"
#include "src/objects-inl.h"
#include "src/handles.h"
#include "src/handles-inl.h"
#include "src/heap/heap.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
double Round(double x) {
// Round to three digits.
return floor(x * 1000 + 0.5) / 1000;
}
void CheckEqualRounded(double expected, double actual) {
expected = Round(expected);
actual = Round(actual);
EXPECT_DOUBLE_EQ(expected, actual);
}
TEST(Heap, HeapGrowingFactor) {
CheckEqualRounded(Heap::kMaxHeapGrowingFactor,
Heap::HeapGrowingFactor(34, 1));
CheckEqualRounded(3.553, Heap::HeapGrowingFactor(45, 1));
CheckEqualRounded(2.830, Heap::HeapGrowingFactor(50, 1));
CheckEqualRounded(1.478, Heap::HeapGrowingFactor(100, 1));
CheckEqualRounded(1.193, Heap::HeapGrowingFactor(200, 1));
CheckEqualRounded(1.121, Heap::HeapGrowingFactor(300, 1));
CheckEqualRounded(Heap::HeapGrowingFactor(300, 1),
Heap::HeapGrowingFactor(600, 2));
CheckEqualRounded(Heap::kMinHeapGrowingFactor,
Heap::HeapGrowingFactor(400, 1));
}
} // namespace internal
} // namespace v8

View File

@ -89,6 +89,7 @@
'libplatform/task-queue-unittest.cc',
'libplatform/worker-thread-unittest.cc',
'heap/gc-idle-time-handler-unittest.cc',
'heap/heap-unittest.cc',
'run-all-unittests.cc',
'test-utils.h',
'test-utils.cc',