Commit ef690ca3 authored by heimbuef's avatar heimbuef Committed by Commit bot

Constrain the zone segment pool size

Added a size constraint to the configuration to limit the segment pool.
This will likely fix the memory alerts from small android devices.

BUG=chromium:655129

Review-Url: https://chromiumcodereview.appspot.com/2424393002
Cr-Commit-Position: refs/heads/master@{#40476}
parent 83e66947
......@@ -5686,6 +5686,10 @@ class V8_EXPORT ResourceConstraints {
void set_code_range_size(size_t limit_in_mb) {
code_range_size_ = limit_in_mb;
}
size_t max_zone_pool_size() const { return max_zone_pool_size_; }
void set_max_zone_pool_size(const size_t bytes) {
max_zone_pool_size_ = bytes;
}
private:
int max_semi_space_size_;
......@@ -5693,6 +5697,7 @@ class V8_EXPORT ResourceConstraints {
int max_executable_size_;
uint32_t* stack_limit_;
size_t code_range_size_;
size_t max_zone_pool_size_;
};
......
......@@ -710,13 +710,13 @@ Extension::Extension(const char* name,
CHECK(source != NULL || source_length_ == 0);
}
ResourceConstraints::ResourceConstraints()
: max_semi_space_size_(0),
max_old_space_size_(0),
max_executable_size_(0),
stack_limit_(NULL),
code_range_size_(0) { }
code_range_size_(0),
max_zone_pool_size_(0) {}
void ResourceConstraints::ConfigureDefaults(uint64_t physical_memory,
uint64_t virtual_memory_limit) {
......@@ -736,18 +736,25 @@ void ResourceConstraints::ConfigureDefaults(uint64_t physical_memory,
set_max_semi_space_size(i::Heap::kMaxSemiSpaceSizeLowMemoryDevice);
set_max_old_space_size(i::Heap::kMaxOldSpaceSizeLowMemoryDevice);
set_max_executable_size(i::Heap::kMaxExecutableSizeLowMemoryDevice);
set_max_zone_pool_size(i::AccountingAllocator::kMaxPoolSizeLowMemoryDevice);
} else if (physical_memory <= medium_limit) {
set_max_semi_space_size(i::Heap::kMaxSemiSpaceSizeMediumMemoryDevice);
set_max_old_space_size(i::Heap::kMaxOldSpaceSizeMediumMemoryDevice);
set_max_executable_size(i::Heap::kMaxExecutableSizeMediumMemoryDevice);
set_max_zone_pool_size(
i::AccountingAllocator::kMaxPoolSizeMediumMemoryDevice);
} else if (physical_memory <= high_limit) {
set_max_semi_space_size(i::Heap::kMaxSemiSpaceSizeHighMemoryDevice);
set_max_old_space_size(i::Heap::kMaxOldSpaceSizeHighMemoryDevice);
set_max_executable_size(i::Heap::kMaxExecutableSizeHighMemoryDevice);
set_max_zone_pool_size(
i::AccountingAllocator::kMaxPoolSizeHighMemoryDevice);
} else {
set_max_semi_space_size(i::Heap::kMaxSemiSpaceSizeHugeMemoryDevice);
set_max_old_space_size(i::Heap::kMaxOldSpaceSizeHugeMemoryDevice);
set_max_executable_size(i::Heap::kMaxExecutableSizeHugeMemoryDevice);
set_max_zone_pool_size(
i::AccountingAllocator::kMaxPoolSizeHugeMemoryDevice);
}
if (virtual_memory_limit > 0 && i::kRequiresCodeRange) {
......@@ -766,11 +773,14 @@ void SetResourceConstraints(i::Isolate* isolate,
int old_space_size = constraints.max_old_space_size();
int max_executable_size = constraints.max_executable_size();
size_t code_range_size = constraints.code_range_size();
size_t max_pool_size = constraints.max_zone_pool_size();
if (semi_space_size != 0 || old_space_size != 0 ||
max_executable_size != 0 || code_range_size != 0) {
isolate->heap()->ConfigureHeap(semi_space_size, old_space_size,
max_executable_size, code_range_size);
}
isolate->allocator()->ConfigureSegmentPool(max_pool_size);
if (constraints.stack_limit() != NULL) {
uintptr_t limit = reinterpret_cast<uintptr_t>(constraints.stack_limit());
isolate->stack_guard()->SetStackLimit(limit);
......
......@@ -14,15 +14,14 @@ namespace v8 {
namespace internal {
AccountingAllocator::AccountingAllocator() : unused_segments_mutex_() {
static const size_t kDefaultBucketMaxSize = 5;
memory_pressure_level_.SetValue(MemoryPressureLevel::kNone);
std::fill(unused_segments_heads_,
unused_segments_heads_ +
(1 + kMaxSegmentSizePower - kMinSegmentSizePower),
std::fill(unused_segments_heads_, unused_segments_heads_ + kNumberBuckets,
nullptr);
std::fill(
unused_segments_sizes,
unused_segments_sizes + (1 + kMaxSegmentSizePower - kMinSegmentSizePower),
0);
std::fill(unused_segments_sizes_, unused_segments_sizes_ + kNumberBuckets, 0);
std::fill(unused_segments_max_sizes_,
unused_segments_max_sizes_ + kNumberBuckets, kDefaultBucketMaxSize);
}
AccountingAllocator::~AccountingAllocator() { ClearPool(); }
......@@ -36,6 +35,39 @@ void AccountingAllocator::MemoryPressureNotification(
}
}
void AccountingAllocator::ConfigureSegmentPool(const size_t max_pool_size) {
// The sum of the bytes of one segment of each size.
static const size_t full_size = (size_t(1) << (kMaxSegmentSizePower + 1)) -
(size_t(1) << kMinSegmentSizePower);
size_t fits_fully = max_pool_size / full_size;
base::LockGuard<base::Mutex> lock_guard(&unused_segments_mutex_);
// We assume few zones (less than 'fits_fully' many) to be active at the same
// time. When zones grow regularly, they will keep requesting segments of
// increasing size each time. Therefore we try to get as many segments with an
// equal number of segments of each size as possible.
// The remaining space is used to make more room for an 'incomplete set' of
// segments beginning with the smaller ones.
// This code will work best if the max_pool_size is a multiple of the
// full_size. If max_pool_size is no sum of segment sizes the actual pool
// size might be smaller then max_pool_size. Note that no actual memory gets
// wasted though.
// TODO(heimbuef): Determine better strategy generating a segment sizes
// distribution that is closer to real/benchmark usecases and uses the given
// max_pool_size more efficiently.
size_t total_size = fits_fully * full_size;
for (size_t power = 0; power < kNumberBuckets; ++power) {
if (total_size + (size_t(1) << power) <= max_pool_size) {
unused_segments_max_sizes_[power] = fits_fully + 1;
total_size += size_t(1) << power;
} else {
unused_segments_max_sizes_[power] = fits_fully;
}
}
}
Segment* AccountingAllocator::GetSegment(size_t bytes) {
Segment* result = GetSegmentFromPool(bytes);
if (result == nullptr) {
......@@ -93,7 +125,7 @@ Segment* AccountingAllocator::GetSegmentFromPool(size_t requested_size) {
return nullptr;
}
uint8_t power = kMinSegmentSizePower;
size_t power = kMinSegmentSizePower;
while (requested_size > (static_cast<size_t>(1) << power)) power++;
DCHECK_GE(power, kMinSegmentSizePower + 0);
......@@ -109,7 +141,7 @@ Segment* AccountingAllocator::GetSegmentFromPool(size_t requested_size) {
unused_segments_heads_[power] = segment->next();
segment->set_next(nullptr);
unused_segments_sizes[power]--;
unused_segments_sizes_[power]--;
base::NoBarrier_AtomicIncrement(
&current_pool_size_, -static_cast<base::AtomicWord>(segment->size()));
}
......@@ -128,7 +160,7 @@ bool AccountingAllocator::AddSegmentToPool(Segment* segment) {
if (size < (1 << kMinSegmentSizePower)) return false;
uint8_t power = kMaxSegmentSizePower;
size_t power = kMaxSegmentSizePower;
while (size < (static_cast<size_t>(1) << power)) power--;
......@@ -138,14 +170,14 @@ bool AccountingAllocator::AddSegmentToPool(Segment* segment) {
{
base::LockGuard<base::Mutex> lock_guard(&unused_segments_mutex_);
if (unused_segments_sizes[power] >= kMaxSegmentsPerBucket) {
if (unused_segments_sizes_[power] >= unused_segments_max_sizes_[power]) {
return false;
}
segment->set_next(unused_segments_heads_[power]);
unused_segments_heads_[power] = segment;
base::NoBarrier_AtomicIncrement(&current_pool_size_, size);
unused_segments_sizes[power]++;
unused_segments_sizes_[power]++;
}
return true;
......@@ -154,7 +186,7 @@ bool AccountingAllocator::AddSegmentToPool(Segment* segment) {
void AccountingAllocator::ClearPool() {
base::LockGuard<base::Mutex> lock_guard(&unused_segments_mutex_);
for (uint8_t power = 0; power <= kMaxSegmentSizePower - kMinSegmentSizePower;
for (size_t power = 0; power <= kMaxSegmentSizePower - kMinSegmentSizePower;
power++) {
Segment* current = unused_segments_heads_[power];
while (current) {
......
......@@ -13,12 +13,18 @@
#include "src/base/platform/semaphore.h"
#include "src/base/platform/time.h"
#include "src/zone/zone-segment.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
namespace v8 {
namespace internal {
class V8_EXPORT_PRIVATE AccountingAllocator {
public:
static const size_t kMaxPoolSizeLowMemoryDevice = 8ul * KB;
static const size_t kMaxPoolSizeMediumMemoryDevice = 1ul * MB;
static const size_t kMaxPoolSizeHighMemoryDevice = 2ul * MB;
static const size_t kMaxPoolSizeHugeMemoryDevice = 3ul * MB;
AccountingAllocator();
virtual ~AccountingAllocator();
......@@ -34,17 +40,26 @@ class V8_EXPORT_PRIVATE AccountingAllocator {
size_t GetCurrentPoolSize() const;
void MemoryPressureNotification(MemoryPressureLevel level);
// Configures the zone segment pool size limits so the pool does not
// grow bigger than max_pool_size.
// TODO(heimbuef): Do not accept segments to pool that are larger than
// their size class requires. Sometimes the zones generate weird segments.
void ConfigureSegmentPool(const size_t max_pool_size);
virtual void ZoneCreation(const Zone* zone) {}
virtual void ZoneDestruction(const Zone* zone) {}
private:
static const uint8_t kMinSegmentSizePower = 13;
static const uint8_t kMaxSegmentSizePower = 18;
static const uint8_t kMaxSegmentsPerBucket = 5;
FRIEND_TEST(Zone, SegmentPoolConstraints);
static const size_t kMinSegmentSizePower = 13;
static const size_t kMaxSegmentSizePower = 18;
STATIC_ASSERT(kMinSegmentSizePower <= kMaxSegmentSizePower);
static const size_t kNumberBuckets =
1 + kMaxSegmentSizePower - kMinSegmentSizePower;
// Allocates a new segment. Returns nullptr on failed allocation.
Segment* AllocateSegment(size_t bytes);
void FreeSegment(Segment* memory);
......@@ -57,10 +72,10 @@ class V8_EXPORT_PRIVATE AccountingAllocator {
// Empties the pool and puts all its contents onto the garbage stack.
void ClearPool();
Segment*
unused_segments_heads_[1 + kMaxSegmentSizePower - kMinSegmentSizePower];
Segment* unused_segments_heads_[kNumberBuckets];
size_t unused_segments_sizes[1 + kMaxSegmentSizePower - kMinSegmentSizePower];
size_t unused_segments_sizes_[kNumberBuckets];
size_t unused_segments_max_sizes_[kNumberBuckets];
base::Mutex unused_segments_mutex_;
......
......@@ -129,6 +129,7 @@ v8_executable("unittests") {
"wasm/switch-logic-unittest.cc",
"wasm/wasm-macro-gen-unittest.cc",
"wasm/wasm-module-builder-unittest.cc",
"zone/segmentpool-unittest.cc",
]
if (v8_current_cpu == "arm") {
......
......@@ -117,6 +117,7 @@
'test-utils.h',
'test-utils.cc',
'value-serializer-unittest.cc',
'zone/segmentpool-unittest.cc',
'wasm/asm-types-unittest.cc',
'wasm/ast-decoder-unittest.cc',
'wasm/control-transfer-unittest.cc',
......
// Copyright 2016 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/zone/accounting-allocator.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
TEST(Zone, SegmentPoolConstraints) {
size_t sizes[]{
0, // Corner case
AccountingAllocator::kMaxPoolSizeLowMemoryDevice,
AccountingAllocator::kMaxPoolSizeMediumMemoryDevice,
AccountingAllocator::kMaxPoolSizeHighMemoryDevice,
AccountingAllocator::kMaxPoolSizeHugeMemoryDevice,
GB // Something really large
};
AccountingAllocator allocator;
for (size_t size : sizes) {
allocator.ConfigureSegmentPool(size);
size_t total_size = 0;
for (size_t power = 0; power < AccountingAllocator::kNumberBuckets;
++power) {
total_size +=
allocator.unused_segments_max_sizes_[power] * (size_t(1) << power);
}
EXPECT_LE(total_size, size);
}
}
} // namespace internal
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment