diff --git a/home/home.go b/home/home.go index 898fb4bd..3c2dce58 100644 --- a/home/home.go +++ b/home/home.go @@ -161,6 +161,9 @@ func run(args options) { // configure log level and output configureLogger(args) + // Go memory hacks + memoryUsage(args) + // print the first message after logger is configured log.Println(version()) log.Debug("Current working directory is %s", Context.workDir) diff --git a/home/memory.go b/home/memory.go new file mode 100644 index 00000000..5ca1c12f --- /dev/null +++ b/home/memory.go @@ -0,0 +1,44 @@ +package home + +import ( + "os" + "runtime/debug" + "time" + + "github.com/AdguardTeam/golibs/log" +) + +// memoryUsage implements a couple of not really beautiful hacks which purpose is to +// make OS reclaim the memory freed by AdGuard Home as soon as possible. +// See this for the details on the performance hits & gains: +// https://github.com/AdguardTeam/AdGuardHome/issues/2044#issuecomment-687042211 +func memoryUsage(args options) { + if args.disableMemoryOptimization { + log.Info("Memory optimization is disabled") + return + } + + // Makes Go allocate heap at a slower pace + // By default we keep it at 50% + debug.SetGCPercent(50) + + // madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED + // instead of MADV_FREE on Linux when returning memory to the + // kernel. This is less efficient, but causes RSS numbers to drop + // more quickly. + _ = os.Setenv("GODEBUG", "madvdontneed=1") + + // periodically call "debug.FreeOSMemory" so + // that the OS could reclaim the free memory + go func() { + ticker := time.NewTicker(5 * time.Minute) + for { + select { + case t := <-ticker.C: + t.Second() + log.Debug("Free OS memory") + debug.FreeOSMemory() + } + } + }() +} diff --git a/home/options.go b/home/options.go index 90d356e8..1d8a8a4e 100644 --- a/home/options.go +++ b/home/options.go @@ -24,7 +24,11 @@ type options struct { // runningAsService flag is set to true when options are passed from the service runner runningAsService bool - glinetMode bool // Activate GL-Inet mode + // disableMemoryOptimization - disables memory optimization hacks + // see memoryUsage() function for the details + disableMemoryOptimization bool + + glinetMode bool // Activate GL-Inet compatibility mode } // functions used for their side-effects @@ -151,6 +155,13 @@ var noCheckUpdateArg = arg{ func(o options) []string { return boolSliceOrNil(o.disableUpdate) }, } +var disableMemoryOptimizationArg = arg{ + "Disable memory optimization", + "no-mem-optimization", "", + nil, func(o options) (options, error) { o.disableMemoryOptimization = true; return o, nil }, nil, + func(o options) []string { return boolSliceOrNil(o.disableMemoryOptimization) }, +} + var verboseArg = arg{ "Enable verbose output", "verbose", "v", @@ -194,6 +205,7 @@ func init() { pidfileArg, checkConfigArg, noCheckUpdateArg, + disableMemoryOptimizationArg, verboseArg, glinetArg, versionArg, diff --git a/main.go b/main.go index eebcd5c4..f712d6c0 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,6 @@ package main import ( - "os" - "runtime/debug" - "time" - "github.com/AdguardTeam/AdGuardHome/home" ) @@ -21,32 +17,5 @@ var channel = "release" var goarm = "" func main() { - memoryUsage() - home.Main(version, channel, goarm) } - -// memoryUsage implements a couple of not really beautiful hacks which purpose is to -// make OS reclaim the memory freed by AdGuard Home as soon as possible. -func memoryUsage() { - debug.SetGCPercent(10) - - // madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED - // instead of MADV_FREE on Linux when returning memory to the - // kernel. This is less efficient, but causes RSS numbers to drop - // more quickly. - _ = os.Setenv("GODEBUG", "madvdontneed=1") - - // periodically call "debug.FreeOSMemory" so - // that the OS could reclaim the free memory - go func() { - ticker := time.NewTicker(15 * time.Second) - for { - select { - case t := <-ticker.C: - t.Second() - debug.FreeOSMemory() - } - } - }() -}