| // Copyright 2015 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package main | |
| import ( | |
| "fmt" | |
| "os" | |
| "runtime" | |
| "runtime/debug" | |
| "sync/atomic" | |
| "time" | |
| ) | |
| func init() { | |
| register("GCFairness", GCFairness) | |
| register("GCFairness2", GCFairness2) | |
| register("GCSys", GCSys) | |
| register("GCPhys", GCPhys) | |
| register("DeferLiveness", DeferLiveness) | |
| } | |
| func GCSys() { | |
| runtime.GOMAXPROCS(1) | |
| memstats := new(runtime.MemStats) | |
| runtime.GC() | |
| runtime.ReadMemStats(memstats) | |
| sys := memstats.Sys | |
| runtime.MemProfileRate = 0 // disable profiler | |
| itercount := 100000 | |
| for i := 0; i < itercount; i++ { | |
| workthegc() | |
| } | |
| // Should only be using a few MB. | |
| // We allocated 100 MB or (if not short) 1 GB. | |
| runtime.ReadMemStats(memstats) | |
| if sys > memstats.Sys { | |
| sys = 0 | |
| } else { | |
| sys = memstats.Sys - sys | |
| } | |
| if sys > 16<<20 { | |
| fmt.Printf("using too much memory: %d bytes\n", sys) | |
| return | |
| } | |
| fmt.Printf("OK\n") | |
| } | |
| var sink []byte | |
| func workthegc() []byte { | |
| sink = make([]byte, 1029) | |
| return sink | |
| } | |
| func GCFairness() { | |
| runtime.GOMAXPROCS(1) | |
| f, err := os.Open("/dev/null") | |
| if os.IsNotExist(err) { | |
| // This test tests what it is intended to test only if writes are fast. | |
| // If there is no /dev/null, we just don't execute the test. | |
| fmt.Println("OK") | |
| return | |
| } | |
| if err != nil { | |
| fmt.Println(err) | |
| os.Exit(1) | |
| } | |
| for i := 0; i < 2; i++ { | |
| go func() { | |
| for { | |
| f.Write([]byte(".")) | |
| } | |
| }() | |
| } | |
| time.Sleep(10 * time.Millisecond) | |
| fmt.Println("OK") | |
| } | |
| func GCFairness2() { | |
| // Make sure user code can't exploit the GC's high priority | |
| // scheduling to make scheduling of user code unfair. See | |
| // issue #15706. | |
| runtime.GOMAXPROCS(1) | |
| debug.SetGCPercent(1) | |
| var count [3]int64 | |
| var sink [3]interface{} | |
| for i := range count { | |
| go func(i int) { | |
| for { | |
| sink[i] = make([]byte, 1024) | |
| atomic.AddInt64(&count[i], 1) | |
| } | |
| }(i) | |
| } | |
| // Note: If the unfairness is really bad, it may not even get | |
| // past the sleep. | |
| // | |
| // If the scheduling rules change, this may not be enough time | |
| // to let all goroutines run, but for now we cycle through | |
| // them rapidly. | |
| // | |
| // OpenBSD's scheduler makes every usleep() take at least | |
| // 20ms, so we need a long time to ensure all goroutines have | |
| // run. If they haven't run after 30ms, give it another 1000ms | |
| // and check again. | |
| time.Sleep(30 * time.Millisecond) | |
| var fail bool | |
| for i := range count { | |
| if atomic.LoadInt64(&count[i]) == 0 { | |
| fail = true | |
| } | |
| } | |
| if fail { | |
| time.Sleep(1 * time.Second) | |
| for i := range count { | |
| if atomic.LoadInt64(&count[i]) == 0 { | |
| fmt.Printf("goroutine %d did not run\n", i) | |
| return | |
| } | |
| } | |
| } | |
| 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) | |
| } | |
| // Test that defer closure is correctly scanned when the stack is scanned. | |
| func DeferLiveness() { | |
| var x [10]int | |
| escape(&x) | |
| fn := func() { | |
| if x[0] != 42 { | |
| panic("FAIL") | |
| } | |
| } | |
| defer fn() | |
| x[0] = 42 | |
| runtime.GC() | |
| runtime.GC() | |
| runtime.GC() | |
| } | |
| //go:noinline | |
| func escape(x interface{}) { sink2 = x; sink2 = nil } | |
| var sink2 interface{} |