Skip to content

Commit

Permalink
main: Tweak runtime GC params for Go 1.19.
Browse files Browse the repository at this point in the history
Go 1.19 introduced the ability to specify a soft upper memory limit to
help deal with transient memory spikes without needing to set the GC
percent to an artificially low value.

This takes advantage of that to impose an upper memory limit that leaves
plenty of headroom for the minimum recommended value while taking into
account the max utxo cache size configuration option and increase the GC
percent to the default value of 100% which allows the mem usage to
expand more in between each GC cycle which in turn significantly reduces
the number of GC cycles that need to be performed, particularly while
performing the initial chain sync.

The end result is much less CPU time spent doing garbage collection and
thus reduces the amount of time it takes to perform the initial chain
sync by about 10%.

The update also has the nice side benefit that the `GOGC` environment
variable can be used if an advanced sysadmin really wanted to tune it
since it is no longer being overridden.
  • Loading branch information
davecgh committed Nov 17, 2022
1 parent c851f17 commit dd65955
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 7 deletions.
38 changes: 32 additions & 6 deletions dcrd.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,38 @@ func dcrdMain() error {
dcrdLog.Info("File logging disabled")
}

// Block and transaction processing can cause bursty allocations. This
// limits the garbage collector from excessively overallocating during
// bursts. It does this by tweaking the target GC percent and soft memory
// limit depending on the version of the Go runtime.
//
// Starting with Go 1.19, a soft upper memory limit is imposed that leaves
// plenty of headroom for the minimum recommended value and the target GC
// percentage is left at the default value to significantly reduce the
// number of GC cycles thereby reducing the amount of CPU time spent doing
// garbage collection.
//
// For versions of Go prior to 1.19, the ability to set a soft upper memory
// limit was not available, so the GC percentage is lowered instead which
// has the effect of preventing overallocations at the expense of more
// frequent GC cycles.
//
// These values were arrived at with the help of profiling live usage.
if limits.SupportsMemoryLimit {
// Enforce a soft memory limit for a base amount along with any extra
// utxo cache over and above the default max cache size.
const memLimitBase = (15 * (1 << 30)) / 10 // 1.5 GiB
softMemLimit := int64(memLimitBase)
if cfg.UtxoCacheMaxSize > defaultUtxoCacheMaxSize {
extra := int64(cfg.UtxoCacheMaxSize) - defaultUtxoCacheMaxSize
softMemLimit += extra * (1 << 20)
}
limits.SetMemoryLimit(softMemLimit)
dcrdLog.Infof("Soft memory limit: %s", humanizeBytes(softMemLimit))
} else {
debug.SetGCPercent(20)
}

// Enable http profiling server if requested.
if cfg.Profile != "" {
go func() {
Expand Down Expand Up @@ -228,12 +260,6 @@ func dcrdMain() error {
}

func main() {
// Block and transaction processing can cause bursty allocations. This
// limits the garbage collector from excessively overallocating during
// bursts. This value was arrived at with the help of profiling live
// usage.
debug.SetGCPercent(20)

// Up some limits.
if err := limits.SetLimits(); err != nil {
fmt.Fprintf(os.Stderr, "failed to set limits: %v\n", err)
Expand Down
2 changes: 1 addition & 1 deletion internal/limits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ limits
[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![Doc](https://img.shields.io/badge/doc-reference-blue.svg)](https://pkg.go.dev/github.com/decred/dcrd/internal/limits)

Package limits allows some process limits to be raised.
Package limits modifies process limits depending on the OS and Go runtime.

## Installation and Updating

Expand Down
20 changes: 20 additions & 0 deletions internal/limits/memlimit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

//go:build go1.19
// +build go1.19

package limits

import "runtime/debug"

// SupportsMemoryLimit indicates that a runtime enforced soft memory limit is
// supported starting with Go 1.19.
const SupportsMemoryLimit = true

// SetMemoryLimit configures the runtime to use the provided limit as a soft
// memory limit starting with Go 1.19.
func SetMemoryLimit(limit int64) {
debug.SetMemoryLimit(limit)
}
17 changes: 17 additions & 0 deletions internal/limits/memlimit_old.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

//go:build !go1.19
// +build !go1.19

package limits

// SupportsMemoryLimit indicates that a runtime enforced soft memory limit is
// not supported for versions of Go prior to version 1.19.
const SupportsMemoryLimit = false

// SetMemoryLimit is a no-op on versions of Go prior to version 1.19 since the
// the ability is not supported on those versions.
func SetMemoryLimit(_ int64) {
}
15 changes: 15 additions & 0 deletions log.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,18 @@ func fatalf(str string) {
}
os.Exit(1)
}

// humanizeBytes returns the provided number of bytes in humanized form with IEC
// units (aka binary prefixes such as KiB and MiB).
func humanizeBytes(b int64) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.2f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
}

0 comments on commit dd65955

Please sign in to comment.