Adjusting GC frequency in Go with GOGC and GOMEMLIMIT and checking the behavior with trace
golangAccording to A Guide to the Go Garbage Collector, GC in Go is performed with a way called mark-sweep, which marks objects that are referenced by root objects such as global and local variables recursively, and sweeps not marked ones. The cost increases as the heap size, but since it targets not only newly allocated memory but also existing memory with a long lifetime, reducing the frequency can reduce the load on the CPU.
You can adjust the frequency with GOGC. GC runs when the heap size exceeds a threshold calculated by the following formula. The default value is 100, so the threshold is roughly twice the Live heap, which is the heap size after the last GC, and increasing it will reduce the frequency of GC.
Target heap memory = Live heap + (Live heap + GC roots) * GOGC / 100
Another parameter, GOMEMLIMIT, added in Go 1.19, also works as a GC threshold. By setting a value based on the amount of available memory, you can increase the frequency of GC when memory is about to run out, but this is just a soft limit. Besides, to prevent processing from slowing down due to GC, at most 50% CPU can be used.
Run go tool trace to confirm that these values actually changes the GC frequency.
package main
import (
"fmt"
"time"
)
func main() {
var heapValues []*int64
ticker := time.Tick(50 * time.Nanosecond)
for {
<-ticker
if len(heapValues) > 1000 {
fmt.Println(len(heapValues))
heapValues = heapValues[1000:]
}
heapValues = append(heapValues, new(int64))
}
}
Make sure values are stored on the heap.
$ go build -gcflags '-m'
./main.go:11:21: inlining call to time.Tick
./main.go:15:15: inlining call to fmt.Println
./main.go:15:15: ... argument does not escape
./main.go:15:19: len(heapValues) escapes to heap
./main.go:18:38: new(int64) escapes to heap
Output and parse the trace file.
$ cat main_test.go
package main
import (
"testing"
"time"
)
func TestMain(t *testing.T) {
go main()
time.Sleep(10 * time.Second)
}
$ go test -trace trace.out .
$ go tool trace trace.out
Once completed, a browser will open and you can check the visualized trace.
Allocated, which is represented by the orange part of Heap, increases, NextGC decreases everyabout 340ms, GC runs, Allocated decreases, and NextGC recovers.
Setting GOGC=50, the interval became about 130ms.
Additionally, setting GOMEMLIMIT=10MiB, GC ran in every about 40ms.