Commit 4b1447e4 authored by Joakim Bengtsson's avatar Joakim Bengtsson Committed by Commit Bot

Improve V8 GC logic for external memory

The logic for V8 GC normally only takes the external memory growth
since last mark-compact into account. Unfortunately, the amount of
external memory recorded at the end of MC is often too high. The
reason is that it might take a while for the external memory
associated with the GCed objects to be released (e.g. V8 itself post a
task to release external memory for ArrayBuffer backing stores). In a
worst case scenario GC is driven only by external memory and none of
the external memory is released by the end of the MC. Then each MC
will record the external memory at its highest point and the GC logic
will allow the external memory to grow a bit higher each time which
can lead to excessive memory use.

This patch improves the situation a bit by calculating the growth from
the lowest external memory seen since the last MC. That way the growth
calculation will be offset from a level presumably closer to the
intended one (to what it would have been if the external memory
associated with the GCed objects was released during the MC). Now,
this fix is not perfect because it can be thrown off by external
memory growth occurring before the lingering memory is
released. However, it seems to work rather well in practice (e.g. when
playing MSE video on YT).

Bug: v8:10185
Change-Id: Ifcdd87eb45f3ae4a99d2aeec667c3ae4ca9a52b6
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2042711Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarDominik Inführ <dinfuehr@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Dominik Inführ <dinfuehr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66193}
parent e16bd85b
...@@ -160,10 +160,10 @@ class Internals { ...@@ -160,10 +160,10 @@ class Internals {
kNumIsolateDataSlots * kApiSystemPointerSize; kNumIsolateDataSlots * kApiSystemPointerSize;
static const int kExternalMemoryLimitOffset = static const int kExternalMemoryLimitOffset =
kExternalMemoryOffset + kApiInt64Size; kExternalMemoryOffset + kApiInt64Size;
static const int kExternalMemoryAtLastMarkCompactOffset = static const int kExternalMemoryLowSinceMarkCompactOffset =
kExternalMemoryLimitOffset + kApiInt64Size; kExternalMemoryLimitOffset + kApiInt64Size;
static const int kIsolateFastCCallCallerFpOffset = static const int kIsolateFastCCallCallerFpOffset =
kExternalMemoryAtLastMarkCompactOffset + kApiInt64Size; kExternalMemoryLowSinceMarkCompactOffset + kApiInt64Size;
static const int kIsolateFastCCallCallerPcOffset = static const int kIsolateFastCCallCallerPcOffset =
kIsolateFastCCallCallerFpOffset + kApiSystemPointerSize; kIsolateFastCCallCallerFpOffset + kApiSystemPointerSize;
static const int kIsolateStackGuardOffset = static const int kIsolateStackGuardOffset =
......
...@@ -11831,9 +11831,9 @@ int64_t Isolate::AdjustAmountOfExternalAllocatedMemory( ...@@ -11831,9 +11831,9 @@ int64_t Isolate::AdjustAmountOfExternalAllocatedMemory(
reinterpret_cast<uint8_t*>(this) + I::kExternalMemoryOffset); reinterpret_cast<uint8_t*>(this) + I::kExternalMemoryOffset);
int64_t* external_memory_limit = reinterpret_cast<int64_t*>( int64_t* external_memory_limit = reinterpret_cast<int64_t*>(
reinterpret_cast<uint8_t*>(this) + I::kExternalMemoryLimitOffset); reinterpret_cast<uint8_t*>(this) + I::kExternalMemoryLimitOffset);
int64_t* external_memory_at_last_mc = int64_t* external_memory_low_since_mc =
reinterpret_cast<int64_t*>(reinterpret_cast<uint8_t*>(this) + reinterpret_cast<int64_t*>(reinterpret_cast<uint8_t*>(this) +
I::kExternalMemoryAtLastMarkCompactOffset); I::kExternalMemoryLowSinceMarkCompactOffset);
// Embedders are weird: we see both over- and underflows here. Perform the // Embedders are weird: we see both over- and underflows here. Perform the
// addition with unsigned types to avoid undefined behavior. // addition with unsigned types to avoid undefined behavior.
...@@ -11842,23 +11842,22 @@ int64_t Isolate::AdjustAmountOfExternalAllocatedMemory( ...@@ -11842,23 +11842,22 @@ int64_t Isolate::AdjustAmountOfExternalAllocatedMemory(
static_cast<uint64_t>(*external_memory)); static_cast<uint64_t>(*external_memory));
*external_memory = amount; *external_memory = amount;
int64_t allocation_diff_since_last_mc = if (amount < *external_memory_low_since_mc) {
static_cast<int64_t>(static_cast<uint64_t>(*external_memory) - *external_memory_low_since_mc = amount;
static_cast<uint64_t>(*external_memory_at_last_mc)); *external_memory_limit = amount + I::kExternalAllocationSoftLimit;
}
if (change_in_bytes <= 0) return *external_memory;
int64_t allocation_diff_since_last_mc = static_cast<int64_t>(
static_cast<uint64_t>(*external_memory) -
static_cast<uint64_t>(*external_memory_low_since_mc));
// Only check memory pressure and potentially trigger GC if the amount of // Only check memory pressure and potentially trigger GC if the amount of
// external memory increased. // external memory increased.
if (allocation_diff_since_last_mc > kMemoryReducerActivationLimit) { if (allocation_diff_since_last_mc > kMemoryReducerActivationLimit) {
CheckMemoryPressure(); CheckMemoryPressure();
} }
if (amount > *external_memory_limit) {
if (change_in_bytes < 0) {
const int64_t lower_limit =
static_cast<int64_t>(static_cast<uint64_t>(*external_memory_limit) +
static_cast<uint64_t>(change_in_bytes));
if (lower_limit > I::kExternalAllocationSoftLimit) {
*external_memory_limit = lower_limit;
}
} else if (change_in_bytes > 0 && amount > *external_memory_limit) {
ReportExternalAllocationLimitReached(); ReportExternalAllocationLimitReached();
} }
return *external_memory; return *external_memory;
......
...@@ -117,7 +117,7 @@ class IsolateData final { ...@@ -117,7 +117,7 @@ class IsolateData final {
V(kEmbedderDataOffset, Internals::kNumIsolateDataSlots* kSystemPointerSize) \ V(kEmbedderDataOffset, Internals::kNumIsolateDataSlots* kSystemPointerSize) \
V(kExternalMemoryOffset, kInt64Size) \ V(kExternalMemoryOffset, kInt64Size) \
V(kExternalMemoryLlimitOffset, kInt64Size) \ V(kExternalMemoryLlimitOffset, kInt64Size) \
V(kExternalMemoryAtLastMarkCompactOffset, kInt64Size) \ V(kExternalMemoryLowSinceMarkCompactOffset, kInt64Size) \
V(kFastCCallCallerFPOffset, kSystemPointerSize) \ V(kFastCCallCallerFPOffset, kSystemPointerSize) \
V(kFastCCallCallerPCOffset, kSystemPointerSize) \ V(kFastCCallCallerPCOffset, kSystemPointerSize) \
V(kStackGuardOffset, StackGuard::kSizeInBytes) \ V(kStackGuardOffset, StackGuard::kSizeInBytes) \
...@@ -151,7 +151,7 @@ class IsolateData final { ...@@ -151,7 +151,7 @@ class IsolateData final {
int64_t external_memory_limit_ = kExternalAllocationSoftLimit; int64_t external_memory_limit_ = kExternalAllocationSoftLimit;
// Caches the amount of external memory registered at the last MC. // Caches the amount of external memory registered at the last MC.
int64_t external_memory_at_last_mark_compact_ = 0; int64_t external_memory_low_since_mark_compact_ = 0;
// Stores the state of the caller for TurboAssembler::CallCFunction so that // Stores the state of the caller for TurboAssembler::CallCFunction so that
// the sampling CPU profiler can iterate the stack during such calls. These // the sampling CPU profiler can iterate the stack during such calls. These
...@@ -220,8 +220,9 @@ void IsolateData::AssertPredictableLayout() { ...@@ -220,8 +220,9 @@ void IsolateData::AssertPredictableLayout() {
kExternalMemoryOffset); kExternalMemoryOffset);
STATIC_ASSERT(offsetof(IsolateData, external_memory_limit_) == STATIC_ASSERT(offsetof(IsolateData, external_memory_limit_) ==
kExternalMemoryLlimitOffset); kExternalMemoryLlimitOffset);
STATIC_ASSERT(offsetof(IsolateData, external_memory_at_last_mark_compact_) == STATIC_ASSERT(
kExternalMemoryAtLastMarkCompactOffset); offsetof(IsolateData, external_memory_low_since_mark_compact_) ==
kExternalMemoryLowSinceMarkCompactOffset);
STATIC_ASSERT(offsetof(IsolateData, fast_c_call_caller_fp_) == STATIC_ASSERT(offsetof(IsolateData, fast_c_call_caller_fp_) ==
kFastCCallCallerFPOffset); kFastCCallCallerFPOffset);
STATIC_ASSERT(offsetof(IsolateData, fast_c_call_caller_pc_) == STATIC_ASSERT(offsetof(IsolateData, fast_c_call_caller_pc_) ==
......
...@@ -2905,10 +2905,10 @@ void Isolate::CheckIsolateLayout() { ...@@ -2905,10 +2905,10 @@ void Isolate::CheckIsolateLayout() {
CHECK_EQ(static_cast<int>( CHECK_EQ(static_cast<int>(
OFFSET_OF(Isolate, isolate_data_.external_memory_limit_)), OFFSET_OF(Isolate, isolate_data_.external_memory_limit_)),
Internals::kExternalMemoryLimitOffset); Internals::kExternalMemoryLimitOffset);
CHECK_EQ(Internals::kExternalMemoryAtLastMarkCompactOffset % 8, 0); CHECK_EQ(Internals::kExternalMemoryLowSinceMarkCompactOffset % 8, 0);
CHECK_EQ(static_cast<int>(OFFSET_OF( CHECK_EQ(static_cast<int>(OFFSET_OF(
Isolate, isolate_data_.external_memory_at_last_mark_compact_)), Isolate, isolate_data_.external_memory_low_since_mark_compact_)),
Internals::kExternalMemoryAtLastMarkCompactOffset); Internals::kExternalMemoryLowSinceMarkCompactOffset);
} }
void Isolate::ClearSerializerData() { void Isolate::ClearSerializerData() {
......
...@@ -65,7 +65,14 @@ int64_t Heap::external_memory() { ...@@ -65,7 +65,14 @@ int64_t Heap::external_memory() {
} }
void Heap::update_external_memory(int64_t delta) { void Heap::update_external_memory(int64_t delta) {
isolate()->isolate_data()->external_memory_ += delta; const int64_t amount = isolate()->isolate_data()->external_memory_ + delta;
isolate()->isolate_data()->external_memory_ = amount;
if (amount <
isolate()->isolate_data()->external_memory_low_since_mark_compact_) {
isolate()->isolate_data()->external_memory_low_since_mark_compact_ = amount;
isolate()->isolate_data()->external_memory_limit_ =
amount + kExternalAllocationSoftLimit;
}
} }
void Heap::update_external_memory_concurrently_freed(uintptr_t freed) { void Heap::update_external_memory_concurrently_freed(uintptr_t freed) {
...@@ -73,8 +80,8 @@ void Heap::update_external_memory_concurrently_freed(uintptr_t freed) { ...@@ -73,8 +80,8 @@ void Heap::update_external_memory_concurrently_freed(uintptr_t freed) {
} }
void Heap::account_external_memory_concurrently_freed() { void Heap::account_external_memory_concurrently_freed() {
isolate()->isolate_data()->external_memory_ -= update_external_memory(
external_memory_concurrently_freed_; -static_cast<int64_t>(external_memory_concurrently_freed_));
external_memory_concurrently_freed_ = 0; external_memory_concurrently_freed_ = 0;
} }
......
...@@ -1452,7 +1452,7 @@ void Heap::ReportExternalMemoryPressure() { ...@@ -1452,7 +1452,7 @@ void Heap::ReportExternalMemoryPressure() {
kGCCallbackFlagSynchronousPhantomCallbackProcessing | kGCCallbackFlagSynchronousPhantomCallbackProcessing |
kGCCallbackFlagCollectAllExternalMemory); kGCCallbackFlagCollectAllExternalMemory);
if (isolate()->isolate_data()->external_memory_ > if (isolate()->isolate_data()->external_memory_ >
(isolate()->isolate_data()->external_memory_at_last_mark_compact_ + (isolate()->isolate_data()->external_memory_low_since_mark_compact_ +
external_memory_hard_limit())) { external_memory_hard_limit())) {
CollectAllGarbage( CollectAllGarbage(
kReduceMemoryFootprintMask, kReduceMemoryFootprintMask,
...@@ -2160,7 +2160,7 @@ void Heap::RecomputeLimits(GarbageCollector collector) { ...@@ -2160,7 +2160,7 @@ void Heap::RecomputeLimits(GarbageCollector collector) {
if (collector == MARK_COMPACTOR) { if (collector == MARK_COMPACTOR) {
// Register the amount of external allocated memory. // Register the amount of external allocated memory.
isolate()->isolate_data()->external_memory_at_last_mark_compact_ = isolate()->isolate_data()->external_memory_low_since_mark_compact_ =
isolate()->isolate_data()->external_memory_; isolate()->isolate_data()->external_memory_;
isolate()->isolate_data()->external_memory_limit_ = isolate()->isolate_data()->external_memory_limit_ =
isolate()->isolate_data()->external_memory_ + isolate()->isolate_data()->external_memory_ +
...@@ -4761,12 +4761,12 @@ size_t Heap::GlobalSizeOfObjects() { ...@@ -4761,12 +4761,12 @@ size_t Heap::GlobalSizeOfObjects() {
uint64_t Heap::PromotedExternalMemorySize() { uint64_t Heap::PromotedExternalMemorySize() {
IsolateData* isolate_data = isolate()->isolate_data(); IsolateData* isolate_data = isolate()->isolate_data();
if (isolate_data->external_memory_ <= if (isolate_data->external_memory_ <=
isolate_data->external_memory_at_last_mark_compact_) { isolate_data->external_memory_low_since_mark_compact_) {
return 0; return 0;
} }
return static_cast<uint64_t>( return static_cast<uint64_t>(
isolate_data->external_memory_ - isolate_data->external_memory_ -
isolate_data->external_memory_at_last_mark_compact_); isolate_data->external_memory_low_since_mark_compact_);
} }
bool Heap::AllocationLimitOvershotByLargeMargin() { bool Heap::AllocationLimitOvershotByLargeMargin() {
...@@ -4889,7 +4889,7 @@ Heap::IncrementalMarkingLimit Heap::IncrementalMarkingLimitReached() { ...@@ -4889,7 +4889,7 @@ Heap::IncrementalMarkingLimit Heap::IncrementalMarkingLimitReached() {
double gained_since_last_gc = double gained_since_last_gc =
PromotedSinceLastGC() + PromotedSinceLastGC() +
(isolate()->isolate_data()->external_memory_ - (isolate()->isolate_data()->external_memory_ -
isolate()->isolate_data()->external_memory_at_last_mark_compact_); isolate()->isolate_data()->external_memory_low_since_mark_compact_);
double size_before_gc = double size_before_gc =
OldGenerationObjectsAndPromotedExternalMemorySize() - OldGenerationObjectsAndPromotedExternalMemorySize() -
gained_since_last_gc; gained_since_last_gc;
......
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