aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/testdata
diff options
context:
space:
mode:
authorMichael Anthony Knyszek <mknyszek@google.com>2021-11-10 20:14:15 +0000
committerMichael Knyszek <mknyszek@google.com>2021-11-10 20:45:04 +0000
commita881409960a2a8117c99dcc0c91ab74885a3c53a (patch)
tree1bf71875b742d9600bf0aa941fc7d4c57812270a /src/runtime/testdata
parentb2d826c09f0f73cd9dc0022a2b052543e8bf4c06 (diff)
downloadgo-a881409960a2a8117c99dcc0c91ab74885a3c53a.tar.xz
runtime: rewrite TestPhysicalMemoryUtilization
This test changes TestPhysicalMemoryUtilization to be simpler, more robust, and more honest about what's going on. Fixes #49411. Change-Id: I913ef055c6e166c104c62595c1597d44db62018c Reviewed-on: https://go-review.googlesource.com/c/go/+/362978 Trust: Michael Knyszek <mknyszek@google.com> Run-TryBot: Michael Knyszek <mknyszek@google.com> Reviewed-by: David Chase <drchase@google.com>
Diffstat (limited to 'src/runtime/testdata')
-rw-r--r--src/runtime/testdata/testprog/gc.go121
1 files changed, 53 insertions, 68 deletions
diff --git a/src/runtime/testdata/testprog/gc.go b/src/runtime/testdata/testprog/gc.go
index 74732cd9f4..6484c36139 100644
--- a/src/runtime/testdata/testprog/gc.go
+++ b/src/runtime/testdata/testprog/gc.go
@@ -132,81 +132,75 @@ func GCFairness2() {
func GCPhys() {
// This test ensures that heap-growth scavenging is working as intended.
//
- // It sets up a specific scenario: it allocates two pairs of objects whose
- // sizes sum to size. One object in each pair is "small" (though must be
- // large enough to be considered a large object by the runtime) and one is
- // large. The small objects are kept while the large objects are freed,
- // creating two large unscavenged holes in the heap. The heap goal should
- // also be small as a result (so size must be at least as large as the
- // minimum heap size). We then allocate one large object, bigger than both
- // pairs of objects combined. This allocation, because it will tip
- // HeapSys-HeapReleased well above the heap goal, should trigger heap-growth
- // scavenging and scavenge most, if not all, of the large holes we created
- // earlier.
+ // It attempts to construct a sizeable "swiss cheese" heap, with many
+ // allocChunk-sized holes. Then, it triggers a heap growth by trying to
+ // allocate as much memory as would fit in those holes.
+ //
+ // The heap growth should cause a large number of those holes to be
+ // returned to the OS.
+
const (
- // Size must be also large enough to be considered a large
- // object (not in any size-segregated span).
- size = 4 << 20
- split = 64 << 10
- objects = 2
+ allocTotal = 32 << 20
+ allocChunk = 64 << 10
+ allocs = allocTotal / allocChunk
// The page cache could hide 64 8-KiB pages from the scavenger today.
maxPageCache = (8 << 10) * 64
-
- // Reduce GOMAXPROCS down to 4 if it's greater. We need to bound the amount
- // of memory held in the page cache because the scavenger can't reach it.
- // The page cache will hold at most maxPageCache of memory per-P, so this
- // bounds the amount of memory hidden from the scavenger to 4*maxPageCache
- // at most.
- maxProcs = 4
)
- // Set GOGC so that this test operates under consistent assumptions.
+ // Set GC percent just so this test is a little more consistent in the
+ // face of varying environments.
debug.SetGCPercent(100)
- procs := runtime.GOMAXPROCS(-1)
- if procs > maxProcs {
- defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs))
- procs = runtime.GOMAXPROCS(-1)
- }
- // Save objects which we want to survive, and condemn objects which we don't.
- // Note that we condemn objects in this way and release them all at once in
- // order to avoid having the GC start freeing up these objects while the loop
- // is still running and filling in the holes we intend to make.
- saved := make([][]byte, 0, objects+1)
- condemned := make([][]byte, 0, objects)
- for i := 0; i < 2*objects; i++ {
+
+ // Set GOMAXPROCS to 1 to minimize the amount of memory held in the page cache,
+ // and to reduce the chance that the background scavenger gets scheduled.
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
+
+ // Allocate allocTotal bytes of memory in allocChunk byte chunks.
+ // Alternate between whether the chunk will be held live or will be
+ // condemned to GC to create holes in the heap.
+ saved := make([][]byte, allocs/2+1)
+ condemned := make([][]byte, allocs/2)
+ for i := 0; i < allocs; i++ {
+ b := make([]byte, allocChunk)
if i%2 == 0 {
- saved = append(saved, make([]byte, split))
+ saved = append(saved, b)
} else {
- condemned = append(condemned, make([]byte, size-split))
+ condemned = append(condemned, b)
}
}
- condemned = nil
- // Clean up the heap. This will free up every other object created above
- // (i.e. everything in condemned) creating holes in the heap.
- // Also, if the condemned objects are still being swept, its possible that
- // the scavenging that happens as a result of the next allocation won't see
- // the holes at all. We call runtime.GC() twice here so that when we allocate
- // our large object there's no race with sweeping.
- runtime.GC()
+
+ // Run a GC cycle just so we're at a consistent state.
runtime.GC()
- // Perform one big allocation which should also scavenge any holes.
- //
- // The heap goal will rise after this object is allocated, so it's very
- // important that we try to do all the scavenging in a single allocation
- // that exceeds the heap goal. Otherwise the rising heap goal could foil our
- // test.
- saved = append(saved, make([]byte, objects*size))
- // Clean up the heap again just to put it in a known state.
+
+ // Drop the only reference to all the condemned memory.
+ condemned = nil
+
+ // Clear the condemned memory.
runtime.GC()
+
+ // At this point, the background scavenger is likely running
+ // and could pick up the work, so the next line of code doesn't
+ // end up doing anything. That's fine. What's important is that
+ // this test fails somewhat regularly if the runtime doesn't
+ // scavenge on heap growth, and doesn't fail at all otherwise.
+
+ // Make a large allocation that in theory could fit, but won't
+ // because we turned the heap into swiss cheese.
+ saved = append(saved, make([]byte, allocTotal/2))
+
// heapBacked is an estimate of the amount of physical memory used by
// this test. HeapSys is an estimate of the size of the mapped virtual
// address space (which may or may not be backed by physical pages)
// whereas HeapReleased is an estimate of the amount of bytes returned
// to the OS. Their difference then roughly corresponds to the amount
// of virtual address space that is backed by physical pages.
+ //
+ // heapBacked also subtracts out maxPageCache bytes of memory because
+ // this is memory that may be hidden from the scavenger per-P. Since
+ // GOMAXPROCS=1 here, that's fine.
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
- heapBacked := stats.HeapSys - stats.HeapReleased
+ heapBacked := stats.HeapSys - stats.HeapReleased - maxPageCache
// If heapBacked does not exceed the heap goal by more than retainExtraPercent
// then the scavenger is working as expected; the newly-created holes have been
// scavenged immediately as part of the allocations which cannot fit in the holes.
@@ -216,19 +210,9 @@ func GCPhys() {
// to other allocations that happen during this test we may still see some physical
// memory over-use.
overuse := (float64(heapBacked) - float64(stats.HeapAlloc)) / float64(stats.HeapAlloc)
- // Compute the threshold.
- //
- // In theory, this threshold should just be zero, but that's not possible in practice.
- // Firstly, the runtime's page cache can hide up to maxPageCache of free memory from the
- // scavenger per P. To account for this, we increase the threshold by the ratio between the
- // total amount the runtime could hide from the scavenger to the amount of memory we expect
- // to be able to scavenge here, which is (size-split)*objects. This computation is the crux
- // GOMAXPROCS above; if GOMAXPROCS is too high the threshold just becomes 100%+ since the
- // amount of memory being allocated is fixed. Then we add 5% to account for noise, such as
- // other allocations this test may have performed that we don't explicitly account for The
- // baseline threshold here is around 11% for GOMAXPROCS=1, capping out at around 30% for
- // GOMAXPROCS=4.
- threshold := 0.05 + float64(procs)*maxPageCache/float64((size-split)*objects)
+ // Check against our overuse threshold, which is what the scavenger always reserves
+ // to encourage allocation of memory that doesn't need to be faulted in.
+ const threshold = 0.1
if overuse <= threshold {
fmt.Println("OK")
return
@@ -243,6 +227,7 @@ func GCPhys() {
"(alloc: %d, goal: %d, sys: %d, rel: %d, objs: %d)\n", threshold*100, overuse*100,
stats.HeapAlloc, stats.NextGC, stats.HeapSys, stats.HeapReleased, len(saved))
runtime.KeepAlive(saved)
+ runtime.KeepAlive(condemned)
}
// Test that defer closure is correctly scanned when the stack is scanned.