Skip to content

Commit

Permalink
Merge pull request #1 from fangwentong/feat-set-max-gogc
Browse files Browse the repository at this point in the history
feat: enable set max effective GOGC when MaxRAMPercentage is set
  • Loading branch information
fangwentong authored Nov 25, 2024
2 parents 68ab9b3 + ba157c6 commit 846ad45
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
go.work

*_cpu.pprof
vendor/
10 changes: 5 additions & 5 deletions gc_parameter_go1.19.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ func setGCParameter(oldConfig, newConfig Config, logger Logger) {
return
}
if newConfig.MaxRAMPercentage > 0 {
if oldConfig.MaxRAMPercentage == newConfig.MaxRAMPercentage {
// The memory limit has not been changed
return
}
memLimit, err := getMemoryLimit()
if err != nil {
logger.Errorf("gctuner: failed to adjust GC, get memory limit err: %v", err.Error())
return
}
limit := int64(newConfig.MaxRAMPercentage / 100.0 * float64(memLimit))
debug.SetGCPercent(-1) // Disable GC unless the memory limit is reached
gogc := newConfig.GOGC
if gogc == 0 { // gogc is not set
gogc = -1 // Disable GC unless the memory limit is reached
}
debug.SetGCPercent(gogc)
debug.SetMemoryLimit(limit)
logger.Logf("gctuner: set memory limit %v", printMemorySize(uint64(limit)))
return
Expand Down
12 changes: 7 additions & 5 deletions gc_tuner.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ var (
type (
// Config is the configuration for adaptive GC
Config struct {
// GOGC is the target GOGC value if specified,
// It is effective when MaxRAMPercentage == 0,
// the default value is the value read from the `GOGC` environment variable,
// or 100 if the `GOGC` environment variable is not set.
// See debug.SetGCPercent(GOGC)
// GOGC is the maximum effective GOGC if specified.
// When MaxRAMPercentage == 0, the GOGC pararameter will be set to debug.SetGCPercent.
// If MaxRAMPercentage is set, in go1.18 and lower, gctuner dynamically adjusts GOGC(debug.SetGCPercent)
// to meet the target memory limit, the GOGC specified here limits the maximum GOGC value.
// In go1.19 and above, gctuner uses GOMEMLIMIT (debug.SetMemoryLimit) to meet target memory limit,
// if GOGC is specified here, memory will be controlled by both GOGC and GOMEMLIMIT, triggering either of them will trigger GC.
// In general, it is recommended that the user directly use MaxRAMPercentage without setting the GOGC parameter here.
GOGC int `json:"gogc,omitempty" yaml:"gogc,omitempty"`

// MaxRAMPercentage is the maximum memory usage, range (0, 100]
Expand Down
2 changes: 1 addition & 1 deletion gc_tuner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestGetGOGCBasics(t *testing.T) {
},
}
for i, _ := range cases {
result := getGOGC(cases[i].MemoryLimitInPercent, cases[i].TotalSize, cases[i].LiveSize)
result := getGOGC(cases[i].MemoryLimitInPercent, cases[i].TotalSize, cases[i].LiveSize, goGCNoLimit)
if result != cases[i].ExpectedGOGC {
t.Errorf("Failed Test Case #%v - Expected: %v Found: %v", i+1, cases[i].ExpectedGOGC, result)
}
Expand Down
19 changes: 12 additions & 7 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ const (
maxRAMUsagePercentage = 95
minGOGCValue = 50
minHeapSize = 4 << 20 // 4MB
goGCNoLimit = float64(math.MaxInt64)
)

// setGCParameter sets GC parameters
func adjustGOGCByMemoryLimit(oldConfig, newConfig Config, logger Logger) {
if newConfig.MaxRAMPercentage > 0 {
// If MaxRAMPercentage is set, adjust GOGC based on the current heap size and the target memory limit
getCurrentPercentAndChangeGOGC(newConfig.MaxRAMPercentage, logger)
maxGOGC := goGCNoLimit
if newConfig.GOGC > 0 {
maxGOGC = float64(newConfig.GOGC)
}
getCurrentPercentAndChangeGOGC(newConfig.MaxRAMPercentage, maxGOGC, logger)
return
}
if reflect.DeepEqual(oldConfig, newConfig) {
Expand Down Expand Up @@ -57,33 +62,33 @@ func readGOGC() int {
return 100
}

func getCurrentPercentAndChangeGOGC(memoryLimitInPercent float64, logger Logger) {
func getCurrentPercentAndChangeGOGC(memoryLimitInPercent float64, maxGOGC float64, logger Logger) {
totalMemSize, err := getMemoryLimit()
if err != nil {
logger.Errorf("gctuner: failed to adjust GC err: %v", err.Error())
logger.Errorf("gctuner: failed to adjust GC, get memory limit err: %v", err.Error())
return
}

liveHeapSize := memory.GetLiveDatasetSize()

liveSize := math.Max(minHeapSize, float64(liveHeapSize))
newgogc := getGOGC(memoryLimitInPercent, totalMemSize, liveSize)
newgogc := getGOGC(memoryLimitInPercent, totalMemSize, liveSize, maxGOGC)

logger.Logf("gctuner: limit %.2f%% (%s). adjusting GOGC to %d, live+unmarked %s",
memoryLimitInPercent, printMemorySize(uint64(memoryLimitInPercent/100*float64(totalMemSize))),
newgogc, printMemorySize(liveHeapSize))
debug.SetGCPercent(newgogc)
}

func getGOGC(memoryLimitInPercent float64, totalMemSize uint64, liveSize float64) int {
func getGOGC(memoryLimitInPercent float64, totalMemSize uint64, liveSize float64, maxGOGC float64) int {
// hard_target = live_dataset + live_dataset * (GOGC / 100).
// hard_target = memoryLimitInPercent
// live_dataset = memPercent
// Therefore, gogc = (hard_target - live_dataset) / live_dataset * 100
newgogc := calculateGOGC(memoryLimitInPercent, totalMemSize, liveSize)

if newgogc > 0 {
return int(math.Max(newgogc, minGOGCValue))
return int(math.Min(math.Max(newgogc, minGOGCValue), maxGOGC))
}

// If the current memory usage has already exceeded the target threshold, it is impossible to reach the target threshold no matter how GOGC is set
Expand All @@ -92,7 +97,7 @@ func getGOGC(memoryLimitInPercent float64, totalMemSize uint64, liveSize float64
// Considering memory limits, if the maximum allowed memory percentage is maxMemPercent, then the upper limit for GOGC is (maxMemPercent - currentMemPercent) / memPercent * 100.0
// Without considering the use of swap memory, the upper limit for maxMemPercent is 100%. If the out-of-memory killer is enabled, maxMemPercent should be reduced appropriately, such as 95%
defaultGOGC := readGOGC()
maxGOGC := calculateGOGC(maxRAMUsagePercentage, totalMemSize, liveSize)
maxGOGC = math.Min(maxGOGC, calculateGOGC(maxRAMUsagePercentage, totalMemSize, liveSize))

return int(math.Max(minGOGCValue, math.Min(float64(defaultGOGC), maxGOGC)))
}
Expand Down

0 comments on commit 846ad45

Please sign in to comment.