cmd/geth: add --gcpercent flag for GC tuning

Add a --gcpercent flag that overrides the Go runtime's GOGC value at
startup. When not set, the existing cache-based auto-tuning is used.

CPU profiling of the binary trie (EIP-7864) shows 44% of CPU time
spent in garbage collection due to the large live object graph (~25K
InternalNodes). Reducing GC frequency via higher GOGC significantly
improves read-heavy workloads at the cost of higher memory usage.

This flag lets operators tune based on their workload:
- Validators (write-heavy): default or --gcpercent=100
- RPC nodes (read-heavy): --gcpercent=200 or higher
This commit is contained in:
CPerezz 2026-03-17 13:31:26 +01:00
parent 98b13f342f
commit 2c4d669f5d
No known key found for this signature in database
GPG key ID: 62045F34B97177DD
2 changed files with 20 additions and 6 deletions

View file

@ -108,6 +108,7 @@ var (
utils.CacheNoPrefetchFlag,
utils.CachePreimagesFlag,
utils.CacheLogSizeFlag,
utils.GCPercentFlag,
utils.FDLimitFlag,
utils.CryptoKZGFlag,
utils.ListenPortFlag,

View file

@ -528,6 +528,12 @@ var (
Category: flags.PerfCategory,
Value: ethconfig.Defaults.FilterLogCacheSize,
}
GCPercentFlag = &cli.IntFlag{
Name: "gcpercent",
Usage: "Set garbage collection target percentage (GOGC). Higher values reduce GC frequency, improving read throughput at the cost of memory. 0 means use the cache-based default",
Value: 0,
Category: flags.PerfCategory,
}
FDLimitFlag = &cli.IntFlag{
Name: "fdlimit",
Usage: "Raise the open file descriptor resource limit (default = system fd limit)",
@ -1752,12 +1758,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
ctx.Set(CacheFlag.Name, strconv.Itoa(allowance))
}
}
// Ensure Go's GC ignores the database cache for trigger percentage
cache := ctx.Int(CacheFlag.Name)
gogc := max(20, min(100, 100/(float64(cache)/1024)))
log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
godebug.SetGCPercent(int(gogc))
// Set Go's GC target percentage. If --gcpercent is explicitly set, use
// that value directly. Otherwise, compute a default based on cache size
// to prevent the GC from treating the database cache as free memory.
if ctx.IsSet(GCPercentFlag.Name) {
gogc := ctx.Int(GCPercentFlag.Name)
log.Info("Set GC target percentage", "GOGC", gogc)
godebug.SetGCPercent(gogc)
} else {
cache := ctx.Int(CacheFlag.Name)
gogc := max(20, min(100, 100/(float64(cache)/1024)))
log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
godebug.SetGCPercent(int(gogc))
}
if ctx.IsSet(SyncTargetFlag.Name) {
cfg.SyncMode = ethconfig.FullSync // dev sync target forces full sync