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 <ckennelly@google.com>
PiperOrigin-RevId: 1008455755
This commit is contained in:
Nuthatch AgentandChris Kennelly authored and Copybara-Service committed 2026-10-27 16:27:56 -07:00
1 parent 0aed5651ce
commit e18f88dca6
2 files changed
+28 -2

No files matched your search

+3 -2
View File
@@ -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<TrackerType>::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));
+25
View File
@@ -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<int, 4> 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.