From e18f88dca6dbf3e4a4779cd423776eefe12a7ae9 Mon Sep 17 00:00:00 2001 From: Nuthatch Agent Date: Tue, 27 Oct 2026 16:25:55 -0700 Subject: [PATCH] Fix modulo-4 low-bit sampling bias in `HugePageFiller::Contribute`. When contributing a hugepage tracker, the filler steps a 48-bit linear congruential generator via `ExponentialBiased::NextRandom` and samples 1% of trackers for VMA tagging. Taking the remainder of the raw generator state modulo 100 depends on its low two bits, which cycle with period 4 because 100 is a multiple of 4. Starting from a zero seed, only contributions at index 3 modulo 4 could ever be sampled, which excluded three quarters of contributed hugepages. Extract high-order bits via `ExponentialBiased::GetRandom` before taking the remainder modulo 100 so all modulo-4 contribution indices are sampled uniformly, and initialize the generator state to 42. Co-authored-by: Chris Kennelly PiperOrigin-RevId: 1008455755 --- tcmalloc/huge_page_filler.h | 5 +++-- tcmalloc/huge_page_filler_test.cc | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tcmalloc/huge_page_filler.h b/tcmalloc/huge_page_filler.h index d1a84458..ec2055ce 100644 --- a/tcmalloc/huge_page_filler.h +++ b/tcmalloc/huge_page_filler.h @@ -927,7 +927,7 @@ class HugePageFiller { MemoryTagFunction& set_anon_vma_name_; int max_backoff_delay_ ABSL_GUARDED_BY(pageheap_lock) = 1; int current_backoff_delay_ ABSL_GUARDED_BY(pageheap_lock) = 0; - uintptr_t rng_ = 0; + uint64_t rng_ = 42; SubreleaseUnbackedMode subrelease_unbacked_mode_; }; @@ -1224,7 +1224,8 @@ inline void HugePageFiller::Contribute( // Decide whether to sample this tracker for tagging. rng_ = ExponentialBiased::NextRandom(rng_); - pt->SetTagState({.sampled_for_tagging = (rng_ % 100 == 0)}); + pt->SetTagState( + {.sampled_for_tagging = (ExponentialBiased::GetRandom(rng_) % 100 == 0)}); pages_allocated_[type] += pt->used_pages(); TC_ASSERT(!(type == AccessDensityPrediction::kDense && donated)); diff --git a/tcmalloc/huge_page_filler_test.cc b/tcmalloc/huge_page_filler_test.cc index 280c1faf..f4a1aea4 100644 --- a/tcmalloc/huge_page_filler_test.cc +++ b/tcmalloc/huge_page_filler_test.cc @@ -8629,6 +8629,31 @@ TEST_F(FillerTest, PreferBackedFillerPages) { DeleteRaw(tail); } +TEST_F(FillerTest, ContributeSamplesAcrossModuloFourResidues) { + randomize_density_ = false; + set_anon_vma_name_.SetIgnoreName(true); + + const Length kAlloc = kPagesPerHugePage / 2 + Length(1); + SpanAllocInfo sparse_info = {1, AccessDensityPrediction::kSparse}; + + // Raw NextRandom(rng_) % 100 == 0 could only ever sample contributions with + // index % 4 == 3 because 100 % 4 == 0 and the 48-bit LCG low 2 bits cycle + // with period 4. Verify all four mod-4 residues are sampled. + std::array residue_counts = {0, 0, 0, 0}; + for (int i = 0; i < 4000; ++i) { + PAlloc p = AllocateWithSpanAllocInfo(kAlloc, sparse_info); + if (p.pt->GetTagState().sampled_for_tagging) { + ++residue_counts[i % 4]; + } + DeleteRaw(p); + } + for (int r = 0; r < 4; ++r) { + EXPECT_GT(residue_counts[r], 0) << "residue " << r; + } + DrainFreedTrackers(); + CheckStats(); +} + TEST(SkipSubreleaseIntervalsTest, EmptyIsNotEnabled) { // When we have a limit hit, we pass SkipSubreleaseIntervals{} to the // filler. Make sure it doesn't signal that we should skip the limit.