github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/util/gctuner/tuner.go (about) 1 // Copyright 2022 ByteDance Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gctuner 16 17 import ( 18 "math" 19 "os" 20 "runtime/debug" 21 "strconv" 22 "sync/atomic" 23 ) 24 25 var ( 26 maxGCPercent uint32 = 500 27 minGCPercent uint32 = 50 28 ) 29 30 var defaultGCPercent uint32 = 100 31 32 func init() { 33 gogcEnv := os.Getenv("GOGC") 34 gogc, err := strconv.ParseInt(gogcEnv, 10, 32) 35 if err != nil { 36 return 37 } 38 defaultGCPercent = uint32(gogc) 39 } 40 41 // Tuning sets the threshold of heap which will be respect by gc tuner. 42 // When Tuning, the env GOGC will not be take effect. 43 // threshold: disable tuning if threshold == 0 44 func Tuning(threshold uint64) { 45 // disable gc tuner if percent is zero 46 if threshold <= 0 && globalTuner != nil { 47 globalTuner.stop() 48 globalTuner = nil 49 return 50 } 51 52 if globalTuner == nil { 53 globalTuner = newTuner(threshold) 54 return 55 } 56 globalTuner.setThreshold(threshold) 57 } 58 59 // GetGCPercent returns the current GCPercent. 60 func GetGCPercent() uint32 { 61 if globalTuner == nil { 62 return defaultGCPercent 63 } 64 return globalTuner.getGCPercent() 65 } 66 67 // GetMaxGCPercent returns the max gc percent value. 68 func GetMaxGCPercent() uint32 { 69 return atomic.LoadUint32(&maxGCPercent) 70 } 71 72 // SetMaxGCPercent sets the new max gc percent value. 73 func SetMaxGCPercent(n uint32) uint32 { 74 return atomic.SwapUint32(&maxGCPercent, n) 75 } 76 77 // GetMinGCPercent returns the min gc percent value. 78 func GetMinGCPercent() uint32 { 79 return atomic.LoadUint32(&minGCPercent) 80 } 81 82 // SetMinGCPercent sets the new min gc percent value. 83 func SetMinGCPercent(n uint32) uint32 { 84 return atomic.SwapUint32(&minGCPercent, n) 85 } 86 87 // only allow one gc tuner in one process 88 var globalTuner *tuner = nil 89 90 /* Heap 91 _______________ => limit: host/cgroup memory hard limit 92 | | 93 |---------------| => threshold: increase GCPercent when gc_trigger < threshold 94 | | 95 |---------------| => gc_trigger: heap_live + heap_live * GCPercent / 100 96 | | 97 |---------------| 98 | heap_live | 99 |_______________| 100 101 Go runtime only trigger GC when hit gc_trigger which affected by GCPercent and heap_live. 102 So we can change GCPercent dynamically to tuning GC performance. 103 */ 104 type tuner struct { 105 finalizer *finalizer 106 gcPercent uint32 107 threshold uint64 // high water level, in bytes 108 } 109 110 // tuning check the memory inuse and tune GC percent dynamically. 111 // Go runtime ensure that it will be called serially. 112 func (t *tuner) tuning() { 113 inuse := readMemoryInuse() 114 threshold := t.getThreshold() 115 // stop gc tuning 116 if threshold <= 0 { 117 return 118 } 119 t.setGCPercent(calcGCPercent(inuse, threshold)) 120 return 121 } 122 123 // threshold = inuse + inuse * (gcPercent / 100) 124 // => gcPercent = (threshold - inuse) / inuse * 100 125 // if threshold < inuse*2, so gcPercent < 100, and GC positively to avoid OOM 126 // if threshold > inuse*2, so gcPercent > 100, and GC negatively to reduce GC times 127 func calcGCPercent(inuse, threshold uint64) uint32 { 128 // invalid params 129 if inuse == 0 || threshold == 0 { 130 return defaultGCPercent 131 } 132 // inuse heap larger than threshold, use min percent 133 if threshold <= inuse { 134 return minGCPercent 135 } 136 gcPercent := uint32(math.Floor(float64(threshold-inuse) / float64(inuse) * 100)) 137 if gcPercent < minGCPercent { 138 return minGCPercent 139 } else if gcPercent > maxGCPercent { 140 return maxGCPercent 141 } 142 return gcPercent 143 } 144 145 func newTuner(threshold uint64) *tuner { 146 t := &tuner{ 147 gcPercent: defaultGCPercent, 148 threshold: threshold, 149 } 150 t.finalizer = newFinalizer(t.tuning) // start tuning 151 return t 152 } 153 154 func (t *tuner) stop() { 155 t.finalizer.stop() 156 } 157 158 func (t *tuner) setThreshold(threshold uint64) { 159 atomic.StoreUint64(&t.threshold, threshold) 160 } 161 162 func (t *tuner) getThreshold() uint64 { 163 return atomic.LoadUint64(&t.threshold) 164 } 165 166 func (t *tuner) setGCPercent(percent uint32) uint32 { 167 atomic.StoreUint32(&t.gcPercent, percent) 168 return uint32(debug.SetGCPercent(int(percent))) 169 } 170 171 func (t *tuner) getGCPercent() uint32 { 172 return atomic.LoadUint32(&t.gcPercent) 173 }