diff --git a/dcrd.go b/dcrd.go index 2c8d9a290a..5ec101f05a 100644 --- a/dcrd.go +++ b/dcrd.go @@ -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() { @@ -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) diff --git a/internal/limits/README.md b/internal/limits/README.md index a9a2f738ef..e9cb185ce8 100644 --- a/internal/limits/README.md +++ b/internal/limits/README.md @@ -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 diff --git a/internal/limits/memlimit.go b/internal/limits/memlimit.go new file mode 100644 index 0000000000..2c8976d725 --- /dev/null +++ b/internal/limits/memlimit.go @@ -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) +} diff --git a/internal/limits/memlimit_old.go b/internal/limits/memlimit_old.go new file mode 100644 index 0000000000..e0f5cf49cf --- /dev/null +++ b/internal/limits/memlimit_old.go @@ -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) { +} diff --git a/log.go b/log.go index 4149604d30..eb4be6dacc 100644 --- a/log.go +++ b/log.go @@ -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]) +}