github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/base/base.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package base 6 7 import ( 8 "fmt" 9 "os" 10 "runtime" 11 "runtime/debug" 12 "runtime/metrics" 13 ) 14 15 var atExitFuncs []func() 16 17 func AtExit(f func()) { 18 atExitFuncs = append(atExitFuncs, f) 19 } 20 21 func Exit(code int) { 22 for i := len(atExitFuncs) - 1; i >= 0; i-- { 23 f := atExitFuncs[i] 24 atExitFuncs = atExitFuncs[:i] 25 f() 26 } 27 os.Exit(code) 28 } 29 30 // To enable tracing support (-t flag), set EnableTrace to true. 31 const EnableTrace = false 32 33 // forEachGC calls fn each GC cycle until it returns false. 34 func forEachGC(fn func() bool) { 35 type T [32]byte // large enough to avoid runtime's tiny object allocator 36 37 var finalizer func(*T) 38 finalizer = func(p *T) { 39 if fn() { 40 runtime.SetFinalizer(p, finalizer) 41 } 42 } 43 44 finalizer(new(T)) 45 } 46 47 // AdjustStartingHeap modifies GOGC so that GC should not occur until the heap 48 // grows to the requested size. This is intended but not promised, though it 49 // is true-mostly, depending on when the adjustment occurs and on the 50 // compiler's input and behavior. Once this size is approximately reached 51 // GOGC is reset to 100; subsequent GCs may reduce the heap below the requested 52 // size, but this function does not affect that. 53 // 54 // -d=gcadjust=1 enables logging of GOGC adjustment events. 55 // 56 // NOTE: If you think this code would help startup time in your own 57 // application and you decide to use it, please benchmark first to see if it 58 // actually works for you (it may not: the Go compiler is not typical), and 59 // whatever the outcome, please leave a comment on bug #56546. This code 60 // uses supported interfaces, but depends more than we like on 61 // current+observed behavior of the garbage collector, so if many people need 62 // this feature, we should consider/propose a better way to accomplish it. 63 func AdjustStartingHeap(requestedHeapGoal uint64) { 64 logHeapTweaks := Debug.GCAdjust == 1 65 mp := runtime.GOMAXPROCS(0) 66 gcConcurrency := Flag.LowerC 67 68 const ( 69 goal = "/gc/heap/goal:bytes" 70 count = "/gc/cycles/total:gc-cycles" 71 allocs = "/gc/heap/allocs:bytes" 72 frees = "/gc/heap/frees:bytes" 73 ) 74 75 sample := []metrics.Sample{{Name: goal}, {Name: count}, {Name: allocs}, {Name: frees}} 76 const ( 77 GOAL = 0 78 COUNT = 1 79 ALLOCS = 2 80 FREES = 3 81 ) 82 83 // Assumptions and observations of Go's garbage collector, as of Go 1.17-1.20: 84 85 // - the initial heap goal is 4M, by fiat. It is possible for Go to start 86 // with a heap as small as 512k, so this may change in the future. 87 88 // - except for the first heap goal, heap goal is a function of 89 // observed-live at the previous GC and current GOGC. After the first 90 // GC, adjusting GOGC immediately updates GOGC; before the first GC, 91 // adjusting GOGC does not modify goal (but the change takes effect after 92 // the first GC). 93 94 // - the before/after first GC behavior is not guaranteed anywhere, it's 95 // just behavior, and it's a bad idea to rely on it. 96 97 // - we don't know exactly when GC will run, even after we adjust GOGC; the 98 // first GC may not have happened yet, may have already happened, or may 99 // be currently in progress, and GCs can start for several reasons. 100 101 // - forEachGC above will run the provided function at some delay after each 102 // GC's mark phase terminates; finalizers are run after marking as the 103 // spans containing finalizable objects are swept, driven by GC 104 // background activity and allocation demand. 105 106 // - "live at last GC" is not available through the current metrics 107 // interface. Instead, live is estimated by knowing the adjusted value of 108 // GOGC and the new heap goal following a GC (this requires knowing that 109 // at least one GC has occurred): 110 // estLive = 100 * newGoal / (100 + currentGogc) 111 // this new value of GOGC 112 // newGogc = 100*requestedHeapGoal/estLive - 100 113 // will result in the desired goal. The logging code checks that the 114 // resulting goal is correct. 115 116 // There's a small risk that the finalizer will be slow to run after a GC 117 // that expands the goal to a huge value, and that this will lead to 118 // out-of-memory. This doesn't seem to happen; in experiments on a variety 119 // of machines with a variety of extra loads to disrupt scheduling, the 120 // worst overshoot observed was 50% past requestedHeapGoal. 121 122 metrics.Read(sample) 123 for _, s := range sample { 124 if s.Value.Kind() == metrics.KindBad { 125 // Just return, a slightly slower compilation is a tolerable outcome. 126 if logHeapTweaks { 127 fmt.Fprintf(os.Stderr, "GCAdjust: Regret unexpected KindBad for metric %s\n", s.Name) 128 } 129 return 130 } 131 } 132 133 // Tinker with GOGC to make the heap grow rapidly at first. 134 currentGoal := sample[GOAL].Value.Uint64() // Believe this will be 4MByte or less, perhaps 512k 135 myGogc := 100 * requestedHeapGoal / currentGoal 136 if myGogc <= 150 { 137 return 138 } 139 140 if logHeapTweaks { 141 sample := append([]metrics.Sample(nil), sample...) // avoid races with GC callback 142 AtExit(func() { 143 metrics.Read(sample) 144 goal := sample[GOAL].Value.Uint64() 145 count := sample[COUNT].Value.Uint64() 146 oldGogc := debug.SetGCPercent(100) 147 if oldGogc == 100 { 148 fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %d gogc %d count %d maxprocs %d gcConcurrency %d\n", 149 goal, oldGogc, count, mp, gcConcurrency) 150 } else { 151 inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() 152 overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) 153 fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %d gogc %d count %d maxprocs %d gcConcurrency %d overPct %d\n", 154 goal, oldGogc, count, mp, gcConcurrency, overPct) 155 156 } 157 }) 158 } 159 160 debug.SetGCPercent(int(myGogc)) 161 162 adjustFunc := func() bool { 163 164 metrics.Read(sample) 165 goal := sample[GOAL].Value.Uint64() 166 count := sample[COUNT].Value.Uint64() 167 168 if goal <= requestedHeapGoal { // Stay the course 169 if logHeapTweaks { 170 fmt.Fprintf(os.Stderr, "GCAdjust: Reuse GOGC adjust, current goal %d, count is %d, current gogc %d\n", 171 goal, count, myGogc) 172 } 173 return true 174 } 175 176 // Believe goal has been adjusted upwards, else it would be less-than-or-equal than requestedHeapGoal 177 calcLive := 100 * goal / (100 + myGogc) 178 179 if 2*calcLive < requestedHeapGoal { // calcLive can exceed requestedHeapGoal! 180 myGogc = 100*requestedHeapGoal/calcLive - 100 181 182 if myGogc > 125 { 183 // Not done growing the heap. 184 oldGogc := debug.SetGCPercent(int(myGogc)) 185 186 if logHeapTweaks { 187 // Check that the new goal looks right 188 inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() 189 metrics.Read(sample) 190 newGoal := sample[GOAL].Value.Uint64() 191 pctOff := 100 * (int64(newGoal) - int64(requestedHeapGoal)) / int64(requestedHeapGoal) 192 // Check that the new goal is close to requested. 3% of make.bash fails this test. Why, TBD. 193 if pctOff < 2 { 194 fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %d, count is %d, gogc was %d, is now %d, calcLive %d pctOff %d\n", 195 goal, count, oldGogc, myGogc, calcLive, pctOff) 196 } else { 197 // The GC is being annoying and not giving us the goal that we requested, say more to help understand when/why. 198 fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %d, count is %d, gogc was %d, is now %d, calcLive %d pctOff %d inUse %d\n", 199 goal, count, oldGogc, myGogc, calcLive, pctOff, inUse) 200 } 201 } 202 return true 203 } 204 } 205 206 // In this case we're done boosting GOGC, set it to 100 and don't set a new finalizer. 207 oldGogc := debug.SetGCPercent(100) 208 // inUse helps estimate how late the finalizer ran; at the instant the previous GC ended, 209 // it was (in theory) equal to the previous GC's heap goal. In a growing heap it is 210 // expected to grow to the new heap goal. 211 inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() 212 overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) 213 if logHeapTweaks { 214 fmt.Fprintf(os.Stderr, "GCAdjust: Reset GOGC adjust, old goal %d, count is %d, gogc was %d, calcLive %d inUse %d overPct %d\n", 215 goal, count, oldGogc, calcLive, inUse, overPct) 216 } 217 return false 218 } 219 220 forEachGC(adjustFunc) 221 }