]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: add physical memory scavenging test
authorMichael Anthony Knyszek <mknyszek@google.com>
Mon, 1 Oct 2018 19:58:01 +0000 (19:58 +0000)
committerMichael Knyszek <mknyszek@google.com>
Tue, 30 Oct 2018 15:44:23 +0000 (15:44 +0000)
This change introduces a test to malloc_test which checks for overuse
of physical memory in the large object treap. Due to fragmentation,
there may be many pages of physical memory that are sitting unused in
large-object space.

For #14045.

Change-Id: I3722468f45063b11246dde6301c7ad02ae34be55
Reviewed-on: https://go-review.googlesource.com/c/138918
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
src/runtime/malloc_test.go
src/runtime/testdata/testprog/gc.go

index e6afc25ea9b72be77a511667620bc0c14dfc2c48..f25bfa48aff029b638e41683bf8be9fb1b0517bd 100644 (file)
@@ -168,6 +168,14 @@ func TestTinyAlloc(t *testing.T) {
        }
 }
 
+func TestPhysicalMemoryUtilization(t *testing.T) {
+       got := runTestProg(t, "testprog", "GCPhys")
+       want := "OK\n"
+       if got != want {
+               t.Fatalf("expected %q, but got %q", want, got)
+       }
+}
+
 type acLink struct {
        x [1 << 20]byte
 }
index 3ca74ba5feefab20803586922b714fb18c2f1fb0..fdf08be7e91714c9bf3e858aa82ee091cd8e34f9 100644 (file)
@@ -17,6 +17,7 @@ func init() {
        register("GCFairness", GCFairness)
        register("GCFairness2", GCFairness2)
        register("GCSys", GCSys)
+       register("GCPhys", GCPhys)
 }
 
 func GCSys() {
@@ -124,3 +125,85 @@ func GCFairness2() {
        }
        fmt.Println("OK")
 }
+
+var maybeSaved []byte
+
+func GCPhys() {
+       // In this test, we construct a very specific scenario. We first
+       // allocate N objects and drop half of their pointers on the floor,
+       // effectively creating N/2 'holes' in our allocated arenas. We then
+       // try to allocate objects twice as big. At the end, we measure the
+       // physical memory overhead of large objects.
+       //
+       // The purpose of this test is to ensure that the GC scavenges free
+       // spans eagerly to ensure high physical memory utilization even
+       // during fragmentation.
+       const (
+               // Unfortunately, measuring actual used physical pages is
+               // difficult because HeapReleased doesn't include the parts
+               // of an arena that haven't yet been touched. So, we just
+               // make objects and size sufficiently large such that even
+               // 64 MB overhead is relatively small in the final
+               // calculation.
+               //
+               // Currently, we target 480MiB worth of memory for our test,
+               // computed as size * objects + (size*2) * (objects/2)
+               // = 2 * size * objects
+               //
+               // Size must be also large enough to be considered a large
+               // object (not in any size-segregated span).
+               size    = 1 << 20
+               objects = 240
+       )
+       // 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)
+       condemned := make([][]byte, 0, objects/2+1)
+       for i := 0; i < objects; i++ {
+               // Write into a global, to prevent this from being optimized away by
+               // the compiler in the future.
+               maybeSaved = make([]byte, size)
+               if i%2 == 0 {
+                       saved = append(saved, maybeSaved)
+               } else {
+                       condemned = append(condemned, maybeSaved)
+               }
+       }
+       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.
+       runtime.GC()
+       // Allocate many new objects of 2x size.
+       for i := 0; i < objects/2; i++ {
+               saved = append(saved, make([]byte, size*2))
+       }
+       // Clean up the heap again just to put it in a known state.
+       runtime.GC()
+       // 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.
+       var stats runtime.MemStats
+       runtime.ReadMemStats(&stats)
+       heapBacked := stats.HeapSys - stats.HeapReleased
+       // If heapBacked exceeds the amount of memory actually used for heap
+       // allocated objects by 10% (post-GC HeapAlloc should be quite close to
+       // the size of the working set), then fail.
+       //
+       // In the context of this test, that indicates a large amount of
+       // fragmentation with physical pages that are otherwise unused but not
+       // returned to the OS.
+       overuse := (float64(heapBacked) - float64(stats.HeapAlloc)) / float64(stats.HeapAlloc)
+       if overuse > 0.1 {
+               fmt.Printf("exceeded physical memory overuse threshold of 10%%: %3.2f%%\n"+
+                       "(alloc: %d, sys: %d, rel: %d, objs: %d)\n", overuse*100, stats.HeapAlloc,
+                       stats.HeapSys, stats.HeapReleased, len(saved))
+               return
+       }
+       fmt.Println("OK")
+       runtime.KeepAlive(saved)
+}