From e8716309e13b389e69fcf42f322a74ef72f2fe9c Mon Sep 17 00:00:00 2001 From: Austin Burdine Date: Wed, 14 Oct 2026 11:01:32 -0400 Subject: [PATCH] deps: V8: backport 2444c109f4f8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [heap] Treat zero allocation throughput as a low allocation rate Since https://crrev.com/c/5935313 allocation throughput is tracked with SmoothedBytesAndDuration, which has no lower bound. A sampled throughput of exactly 0 -- nothing was allocated, or the smoothed value decayed to 0 after a long idle period -- made ComputeMutatorUtilizationImpl() return a mutator utilization of 0 ("maximal allocation"). For an isolate that never allocates on the CppHeap, HasLowAllocationRate() is then never true and the MemoryReducer cannot start a GC through its low-allocation condition; only the 100s watchdog can. Node.js hits this for any process that does not use node:vm. Remove the special case. A mutator speed of 0 now yields a mutator utilization of 1, i.e. the lowest possible allocation rate. gc_speed is either the conservative default or at least 1 byte/ms, so the formula stays well defined. Bug: 570738027 Change-Id: I84cd51607df3c31c2f1e02bb2fe258e5506131ce Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8521518 Reviewed-by: Dominik Inführ Commit-Queue: Dominik Inführ Reviewed-by: Omer Katz Commit-Queue: Omer Katz Cr-Commit-Position: refs/heads/main@{#110599} Refs: https://github.com/v8/v8/commit/2444c109f4f8165024971e15b12eb4611b4cd676 Fixes: https://github.com/nodejs/node/issues/66564 Signed-off-by: Austin Burdine --- common.gypi | 2 +- deps/v8/AUTHORS | 1 + deps/v8/src/heap/gc-tracer.h | 2 + deps/v8/src/heap/heap.cc | 2 - deps/v8/src/heap/heap.h | 2 +- .../test/unittests/heap/gc-tracer-unittest.cc | 37 +++++++++++++++++++ 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/common.gypi b/common.gypi index 39229ef58787..2967d1e63a48 100644 --- a/common.gypi +++ b/common.gypi @@ -44,7 +44,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.39', + 'v8_embedder_string': '-node.40', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/AUTHORS b/deps/v8/AUTHORS index 404f89019900..c5c4fa07ee1c 100644 --- a/deps/v8/AUTHORS +++ b/deps/v8/AUTHORS @@ -77,6 +77,7 @@ Artem Kobzar Arthur Islamov Asuka Shikina Aurèle Barrière +Austin Burdine Aviv Keller Bala Avulapati Bangfu Tao diff --git a/deps/v8/src/heap/gc-tracer.h b/deps/v8/src/heap/gc-tracer.h index f980e3409811..400457874a29 100644 --- a/deps/v8/src/heap/gc-tracer.h +++ b/deps/v8/src/heap/gc-tracer.h @@ -648,6 +648,7 @@ class V8_EXPORT_PRIVATE GCTracer { FRIEND_TEST(GCTracerTest, BackgroundMajorMCScope); FRIEND_TEST(GCTracerTest, CyclePriorities); FRIEND_TEST(GCTracerTest, EmbedderAllocationThroughput); + FRIEND_TEST(GCTracerTest, HighAllocationRateWithEmbedderAllocation); FRIEND_TEST(GCTracerTest, MultithreadedBackgroundScope); FRIEND_TEST(GCTracerTest, NewSpaceAllocationThroughput); FRIEND_TEST(GCTracerTest, PerGenerationAllocationThroughput); @@ -656,6 +657,7 @@ class V8_EXPORT_PRIVATE GCTracer { FRIEND_TEST(GCTracerTest, IncrementalMarkingDetails); FRIEND_TEST(GCTracerTest, IncrementalScope); FRIEND_TEST(GCTracerTest, IncrementalMarkingSpeed); + FRIEND_TEST(GCTracerTest, LowAllocationRateWithZeroEmbedderAllocation); FRIEND_TEST(GCTracerTest, MutatorUtilization); FRIEND_TEST(GCTracerTest, RecordMarkCompactHistograms); FRIEND_TEST(GCTracerTest, RecordScavengerHistograms); diff --git a/deps/v8/src/heap/heap.cc b/deps/v8/src/heap/heap.cc index 82682a78cd7e..9471e5778800 100644 --- a/deps/v8/src/heap/heap.cc +++ b/deps/v8/src/heap/heap.cc @@ -3572,9 +3572,7 @@ namespace { double ComputeMutatorUtilizationImpl(double mutator_speed, std::optional gc_speed) { - constexpr double kMinMutatorUtilization = 0.0; constexpr double kConservativeGcSpeedInBytesPerMillisecond = 200000; - if (mutator_speed == 0) return kMinMutatorUtilization; if (!gc_speed) gc_speed = kConservativeGcSpeedInBytesPerMillisecond; // Derivation: // mutator_utilization = mutator_time / (mutator_time + gc_time) diff --git a/deps/v8/src/heap/heap.h b/deps/v8/src/heap/heap.h index c51facdc2cf7..8aa77cb115fe 100644 --- a/deps/v8/src/heap/heap.h +++ b/deps/v8/src/heap/heap.h @@ -674,7 +674,7 @@ class Heap final { // null. V8_INLINE bool CanSafepoint() const { return deserialization_complete(); } - bool HasLowAllocationRate(); + V8_EXPORT_PRIVATE bool HasLowAllocationRate(); bool HasHighFragmentation(); void ActivateMemoryReducerIfNeeded(); diff --git a/deps/v8/test/unittests/heap/gc-tracer-unittest.cc b/deps/v8/test/unittests/heap/gc-tracer-unittest.cc index 5f31ddcd812e..1fb48450e54f 100644 --- a/deps/v8/test/unittests/heap/gc-tracer-unittest.cc +++ b/deps/v8/test/unittests/heap/gc-tracer-unittest.cc @@ -141,6 +141,43 @@ TEST_F(GCTracerTest, PerGenerationAllocationThroughput) { tracer->EmbedderAllocationThroughputInBytesPerMillisecond())); } +TEST_F(GCTracerTest, LowAllocationRateWithZeroEmbedderAllocation) { + if (v8_flags.stress_incremental_marking) return; + Heap* heap = i_isolate()->heap(); + GCTracer* tracer = heap->tracer(); + tracer->ResetForTesting(); + tracer->allocation_time_ = base::TimeTicks(); + + // Two samples 100 ms apart. Young and old generation allocate a little; + // the embedder (CppHeap) allocates nothing at all, as in an embedder that + // never uses cppgc (e.g. Node.js without node:vm). + tracer->SampleAllocation(base::TimeTicks::FromMsTicksForTesting(0), 0, 0, 0); + tracer->SampleAllocation(base::TimeTicks::FromMsTicksForTesting(100), 100, + 100, 0); + + EXPECT_DOUBLE_EQ(0.0, + tracer->EmbedderAllocationThroughputInBytesPerMillisecond()); + EXPECT_TRUE(heap->HasLowAllocationRate()); +} + +TEST_F(GCTracerTest, HighAllocationRateWithEmbedderAllocation) { + if (v8_flags.stress_incremental_marking) return; + Heap* heap = i_isolate()->heap(); + GCTracer* tracer = heap->tracer(); + tracer->ResetForTesting(); + tracer->allocation_time_ = base::TimeTicks(); + + // Same as above, but the embedder allocates heavily: 1 GB over 100 ms is + // ~10^7 B/ms, so the embedder term must still report a high allocation + // rate (mu = 200000 / (10^7 + 200000) < 0.993). + tracer->SampleAllocation(base::TimeTicks::FromMsTicksForTesting(0), 0, 0, 0); + tracer->SampleAllocation(base::TimeTicks::FromMsTicksForTesting(100), 100, + 100, size_t{1} << 30); + + EXPECT_GT(tracer->EmbedderAllocationThroughputInBytesPerMillisecond(), 0.0); + EXPECT_FALSE(heap->HasLowAllocationRate()); +} + TEST_F(GCTracerTest, RegularScope) { if (v8_flags.stress_incremental_marking) return; GCTracer* tracer = i_isolate()->heap()->tracer();