From 81d5d59e9bb6cd90ba94cfed0f86a7bea8e82cc7 Mon Sep 17 00:00:00 2001 From: Austin Burdine Date: Mon, 12 Oct 2026 21:00:23 -0400 Subject: [PATCH] deps: V8: cherry-pick 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 --- 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 | 39 +++++++++++++++++++ 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/common.gypi b/common.gypi index 9fc77e9445cb..b1bae4999b26 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.15', + 'v8_embedder_string': '-node.16', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/AUTHORS b/deps/v8/AUTHORS index ee6d6af3a38d..182bc5358591 100644 --- a/deps/v8/AUTHORS +++ b/deps/v8/AUTHORS @@ -79,6 +79,7 @@ Artem Kobzar Arthur Islamov Asuka Shikina Aurèle Barrière +Austin Burdine Aviv Keller Ayako Hayasaka Bala Avulapati diff --git a/deps/v8/src/heap/gc-tracer.h b/deps/v8/src/heap/gc-tracer.h index 453ab94e4b14..ffa5d7d0e68e 100644 --- a/deps/v8/src/heap/gc-tracer.h +++ b/deps/v8/src/heap/gc-tracer.h @@ -692,6 +692,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); @@ -700,6 +701,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 d67b6933c72a..c3e69804cf75 100644 --- a/deps/v8/src/heap/heap.cc +++ b/deps/v8/src/heap/heap.cc @@ -3492,9 +3492,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 810449851fec..9d7aeed3f215 100644 --- a/deps/v8/src/heap/heap.h +++ b/deps/v8/src/heap/heap.h @@ -687,7 +687,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 bcde0440e28f..563ad1de160a 100644 --- a/deps/v8/test/unittests/heap/gc-tracer-unittest.cc +++ b/deps/v8/test/unittests/heap/gc-tracer-unittest.cc @@ -180,6 +180,45 @@ TEST_F(GCTracerTest, PerGenerationAllocationThroughput) { tracer->ExternalAllocationThroughputInBytesPerMillisecond())); } +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) and external memory allocate 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, + 0); + tracer->SampleAllocation(base::TimeTicks::FromMsTicksForTesting(100), 100, + 100, 0, 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, + 0); + tracer->SampleAllocation(base::TimeTicks::FromMsTicksForTesting(100), 100, + 100, size_t{1} << 30, 0); + + 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();