diff options
Diffstat (limited to '8004-runtime-change-gcBackgroundUtilization-optional.patch')
-rw-r--r-- | 8004-runtime-change-gcBackgroundUtilization-optional.patch | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/8004-runtime-change-gcBackgroundUtilization-optional.patch b/8004-runtime-change-gcBackgroundUtilization-optional.patch new file mode 100644 index 0000000..978c028 --- /dev/null +++ b/8004-runtime-change-gcBackgroundUtilization-optional.patch @@ -0,0 +1,442 @@ +From b270dd5d3dc1cef48866a9b29d26a3745e5dee67 Mon Sep 17 00:00:00 2001 +From: jinye <jinye10@huawei.com> +Date: Wed, 6 Aug 2025 17:20:28 +0800 +Subject: [PATCH] runtime:change gcBackgroundUtilization optional + +--- + src/runtime/export_test.go | 9 ++-- + src/runtime/mgc.go | 8 ++-- + src/runtime/mgclimit.go | 2 +- + src/runtime/mgclimit_test.go | 12 +++--- + src/runtime/mgcpacer.go | 80 +++++++++++++++++++++++------------- + src/runtime/mgcpacer_test.go | 24 ++++++++++- + 6 files changed, 90 insertions(+), 45 deletions(-) + +diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go +index 79d83b3a..29341dd2 100644 +--- a/src/runtime/export_test.go ++++ b/src/runtime/export_test.go +@@ -1324,25 +1324,26 @@ func GCTestPointerClass(p unsafe.Pointer) string { + const Raceenabled = raceenabled + + const ( +- GCBackgroundUtilization = gcBackgroundUtilization +- GCGoalUtilization = gcGoalUtilization + DefaultHeapMinimum = defaultHeapMinimum + MemoryLimitHeapGoalHeadroomPercent = memoryLimitHeapGoalHeadroomPercent + MemoryLimitMinHeapGoalHeadroom = memoryLimitMinHeapGoalHeadroom + ) + ++var GCBackgroundUtilization = gcController.gcRatio ++var GCGoalUtilization = gcGoalUtilization ++ + type GCController struct { + gcControllerState + } + +-func NewGCController(gcPercent int, memoryLimit int64) *GCController { ++func NewGCController(gcPercent int, memoryLimit int64, gcRatio float64) *GCController { + // Force the controller to escape. We're going to + // do 64-bit atomics on it, and if it gets stack-allocated + // on a 32-bit architecture, it may get allocated unaligned + // space. + g := Escape(new(GCController)) + g.gcControllerState.test = true // Mark it as a test copy. +- g.init(int32(gcPercent), memoryLimit) ++ g.init(int32(gcPercent), memoryLimit, gcRatio) + return g + } + +diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go +index 48001cfd..b8646679 100644 +--- a/src/runtime/mgc.go ++++ b/src/runtime/mgc.go +@@ -184,7 +184,7 @@ func gcinit() { + // Initialize GC pacer state. + // Use the environment variable GOGC for the initial gcPercent value. + // Use the environment variable GOMEMLIMIT for the initial memoryLimit value. +- gcController.init(readGOGC(), readGOMEMLIMIT()) ++ gcController.init(readGOGC(), readGOMEMLIMIT(), readGOGCRATIO()) + + work.startSema = 1 + work.markDoneSema = 1 +@@ -270,12 +270,12 @@ const ( + + // gcMarkWorkerFractionalMode indicates that a P is currently + // running the "fractional" mark worker. The fractional worker +- // is necessary when GOMAXPROCS*gcBackgroundUtilization is not ++ // is necessary when GOMAXPROCS*gcController.gcRatio is not + // an integer and using only dedicated workers would result in +- // utilization too far from the target of gcBackgroundUtilization. ++ // utilization too far from the target of gcController.gcRatio. + // The fractional worker should run until it is preempted and + // will be scheduled to pick up the fractional part of +- // GOMAXPROCS*gcBackgroundUtilization. ++ // GOMAXPROCS*gcController.gcRatio. + gcMarkWorkerFractionalMode + + // gcMarkWorkerIdleMode indicates that a P is running the mark +diff --git a/src/runtime/mgclimit.go b/src/runtime/mgclimit.go +index ad86fbd6..ae35ee8d 100644 +--- a/src/runtime/mgclimit.go ++++ b/src/runtime/mgclimit.go +@@ -230,7 +230,7 @@ func (l *gcCPULimiterState) updateLocked(now int64) { + // Compute total GC time. + windowGCTime := assistTime + if l.gcEnabled { +- windowGCTime += int64(float64(windowTotalTime) * gcBackgroundUtilization) ++ windowGCTime += int64(float64(windowTotalTime) * gcController.gcRatio) + } + + // Subtract out all idle time from the total time. Do this after computing +diff --git a/src/runtime/mgclimit_test.go b/src/runtime/mgclimit_test.go +index 124da03e..8d3eaa6b 100644 +--- a/src/runtime/mgclimit_test.go ++++ b/src/runtime/mgclimit_test.go +@@ -76,13 +76,13 @@ func TestGCCPULimiter(t *testing.T) { + // Test passing time without assists during a GC. Specifically, just enough to drain the bucket to + // exactly procs nanoseconds (easier to get to because of rounding). + // +- // The window we need to drain the bucket is 1/(1-2*gcBackgroundUtilization) times the current fill: ++ // The window we need to drain the bucket is 1/(1-2*gcController.gcRatio) times the current fill: + // +- // fill + (window * procs * gcBackgroundUtilization - window * procs * (1-gcBackgroundUtilization)) = n +- // fill = n - (window * procs * gcBackgroundUtilization - window * procs * (1-gcBackgroundUtilization)) +- // fill = n + window * procs * ((1-gcBackgroundUtilization) - gcBackgroundUtilization) +- // fill = n + window * procs * (1-2*gcBackgroundUtilization) +- // window = (fill - n) / (procs * (1-2*gcBackgroundUtilization))) ++ // fill + (window * procs * gcController.gcRatio - window * procs * (1-gcController.gcRatio)) = n ++ // fill = n - (window * procs * gcController.gcRatio - window * procs * (1-gcController.gcRatio)) ++ // fill = n + window * procs * ((1-gcController.gcRatio) - gcController.gcRatio) ++ // fill = n + window * procs * (1-2*gcController.gcRatio) ++ // window = (fill - n) / (procs * (1-2*gcController.gcRatio))) + // + // And here we want n=procs: + factor := (1 / (1 - 2*GCBackgroundUtilization)) +diff --git a/src/runtime/mgcpacer.go b/src/runtime/mgcpacer.go +index 3e80fae4..20630c3f 100644 +--- a/src/runtime/mgcpacer.go ++++ b/src/runtime/mgcpacer.go +@@ -12,30 +12,6 @@ import ( + ) + + const ( +- // gcGoalUtilization is the goal CPU utilization for +- // marking as a fraction of GOMAXPROCS. +- // +- // Increasing the goal utilization will shorten GC cycles as the GC +- // has more resources behind it, lessening costs from the write barrier, +- // but comes at the cost of increasing mutator latency. +- gcGoalUtilization = gcBackgroundUtilization +- +- // gcBackgroundUtilization is the fixed CPU utilization for background +- // marking. It must be <= gcGoalUtilization. The difference between +- // gcGoalUtilization and gcBackgroundUtilization will be made up by +- // mark assists. The scheduler will aim to use within 50% of this +- // goal. +- // +- // As a general rule, there's little reason to set gcBackgroundUtilization +- // < gcGoalUtilization. One reason might be in mostly idle applications, +- // where goroutines are unlikely to assist at all, so the actual +- // utilization will be lower than the goal. But this is moot point +- // because the idle mark workers already soak up idle CPU resources. +- // These two values are still kept separate however because they are +- // distinct conceptually, and in previous iterations of the pacer the +- // distinction was more important. +- gcBackgroundUtilization = 0.25 +- + // gcCreditSlack is the amount of scan work credit that can + // accumulate locally before updating gcController.heapScanWork and, + // optionally, gcController.bgScanCredit. Lower values give a more +@@ -72,6 +48,15 @@ const ( + // to maintain the memory limit. + memoryLimitHeapGoalHeadroomPercent = 3 + ) ++// gcGoalUtilization is the goal CPU utilization for ++// marking as a fraction of GOMAXPROCS. ++// ++// Increasing the goal utilization will shorten GC cycles as the GC ++// has more resources behind it, lessening costs from the write barrier, ++// but comes at the cost of increasing mutator latency. ++var gcGoalUtilization = gcController.gcRatio ++ ++ + + // gcController implements the GC pacing controller that determines + // when to trigger concurrent garbage collection and how much marking +@@ -88,6 +73,11 @@ const ( + var gcController gcControllerState + + type gcControllerState struct { ++ // gcController.gcRatio be optional, value equals gcratio/100.0. ++ // Initialized from GOGCRATIO, which in the range of (1, 99). ++ // Default GOGCRATIO is 25. ++ gcRatio float64 ++ + // Initialized from GOGC. GOGC=off means no GC. + gcPercent atomic.Int32 + +@@ -366,11 +356,12 @@ type gcControllerState struct { + _ cpu.CacheLinePad + } + +-func (c *gcControllerState) init(gcPercent int32, memoryLimit int64) { ++func (c *gcControllerState) init(gcPercent int32, memoryLimit int64, gcRatio float64) { + c.heapMinimum = defaultHeapMinimum + c.triggered = ^uint64(0) + c.setGCPercent(gcPercent) + c.setMemoryLimit(memoryLimit) ++ c.setGOGCRatio(gcRatio) + c.commit(true) // No sweep phase in the first GC cycle. + // N.B. Don't bother calling traceHeapGoal. Tracing is never enabled at + // initialization time. +@@ -398,13 +389,13 @@ func (c *gcControllerState) startCycle(markStartTime int64, procs int, trigger g + // dedicated workers so that the utilization is closest to + // 25%. For small GOMAXPROCS, this would introduce too much + // error, so we add fractional workers in that case. +- totalUtilizationGoal := float64(procs) * gcBackgroundUtilization ++ totalUtilizationGoal := float64(procs) * gcController.gcRatio + dedicatedMarkWorkersNeeded := int64(totalUtilizationGoal + 0.5) + utilError := float64(dedicatedMarkWorkersNeeded)/totalUtilizationGoal - 1 + const maxUtilError = 0.3 + if utilError < -maxUtilError || utilError > maxUtilError { + // Rounding put us more than 30% off our goal. With +- // gcBackgroundUtilization of 25%, this happens for ++ // gcController.gcRatio of 25%, this happens for + // GOMAXPROCS<=3 or GOMAXPROCS=6. Enable fractional + // workers to compensate. + if float64(dedicatedMarkWorkersNeeded) > totalUtilizationGoal { +@@ -604,7 +595,7 @@ func (c *gcControllerState) endCycle(now int64, procs int, userForced bool) { + assistDuration := now - c.markStartTime + + // Assume background mark hit its utilization goal. +- utilization := gcBackgroundUtilization ++ utilization := gcController.gcRatio + // Add assist utilization; avoid divide by zero. + if assistDuration > 0 { + utilization += float64(c.assistTime.Load()) / float64(assistDuration*int64(procs)) +@@ -1344,6 +1335,39 @@ func readGOMEMLIMIT() int64 { + return n + } + ++func (c *gcControllerState) setGOGCRatio(in float64) float64 { ++ if !c.test { ++ assertWorldStoppedOrLockHeld(&mheap_.lock) ++ } ++ ++ out := c.gcRatio ++ c.gcRatio = in ++ ++ return out ++} ++ ++func readGOGCRATIO() float64 { ++ p := gogetenv("GOGCRATIO") ++ if p == "" { ++ return 0.25 ++ } ++ n, ok := parseByteCount(p) ++ if !ok { ++ print("GOGCRATIO=", p, "\n") ++ throw("malformed GOGCRATIO; get the wrong value") ++ } ++ ++ if n < 1 { ++ n = 1 ++ } else if n > 99 { ++ n = 99 ++ } ++ ++ out := float64(n)/100.0 ++ ++ return out ++} ++ + // addIdleMarkWorker attempts to add a new idle mark worker. + // + // If this returns true, the caller must become an idle mark worker unless +diff --git a/src/runtime/mgcpacer_test.go b/src/runtime/mgcpacer_test.go +index ef1483d6..32d4943c 100644 +--- a/src/runtime/mgcpacer_test.go ++++ b/src/runtime/mgcpacer_test.go +@@ -24,6 +24,7 @@ func TestGcPacer(t *testing.T) { + name: "Steady", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33.0), +@@ -49,6 +50,7 @@ func TestGcPacer(t *testing.T) { + name: "SteadyBigStacks", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(132.0), +@@ -77,6 +79,7 @@ func TestGcPacer(t *testing.T) { + name: "SteadyBigGlobals", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 128 << 20, + nCores: 8, + allocRate: constant(132.0), +@@ -105,6 +108,7 @@ func TestGcPacer(t *testing.T) { + name: "StepAlloc", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33.0).sum(ramp(66.0, 1).delay(50)), +@@ -128,6 +132,7 @@ func TestGcPacer(t *testing.T) { + name: "HeavyStepAlloc", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33).sum(ramp(330, 1).delay(50)), +@@ -151,6 +156,7 @@ func TestGcPacer(t *testing.T) { + name: "StepScannableFrac", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(128.0), +@@ -176,6 +182,7 @@ func TestGcPacer(t *testing.T) { + name: "HighGOGC", + gcPercent: 1500, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: random(7, 0x53).offset(165), +@@ -217,6 +224,7 @@ func TestGcPacer(t *testing.T) { + name: "OscAlloc", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: oscillate(13, 0, 8).offset(67), +@@ -241,6 +249,7 @@ func TestGcPacer(t *testing.T) { + name: "JitterAlloc", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: random(13, 0xf).offset(132), +@@ -266,6 +275,7 @@ func TestGcPacer(t *testing.T) { + name: "HeavyJitterAlloc", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: random(33.0, 0x0).offset(330), +@@ -295,6 +305,7 @@ func TestGcPacer(t *testing.T) { + name: "SmallHeapSlowAlloc", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(1.0), +@@ -332,6 +343,7 @@ func TestGcPacer(t *testing.T) { + name: "MediumHeapSlowAlloc", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(1.0), +@@ -369,6 +381,7 @@ func TestGcPacer(t *testing.T) { + name: "LargeHeapSlowAlloc", + gcPercent: 100, + memoryLimit: math.MaxInt64, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(1.0), +@@ -407,6 +420,7 @@ func TestGcPacer(t *testing.T) { + name: "SteadyMemoryLimit", + gcPercent: 100, + memoryLimit: 512 << 20, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33.0), +@@ -436,6 +450,7 @@ func TestGcPacer(t *testing.T) { + name: "SteadyMemoryLimitNoGCPercent", + gcPercent: -1, + memoryLimit: 512 << 20, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33.0), +@@ -465,6 +480,7 @@ func TestGcPacer(t *testing.T) { + name: "ExceedMemoryLimit", + gcPercent: 100, + memoryLimit: 512 << 20, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33.0), +@@ -499,6 +515,7 @@ func TestGcPacer(t *testing.T) { + name: "ExceedMemoryLimitNoGCPercent", + gcPercent: -1, + memoryLimit: 512 << 20, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33.0), +@@ -538,6 +555,7 @@ func TestGcPacer(t *testing.T) { + name: "MaintainMemoryLimit", + gcPercent: 100, + memoryLimit: 512 << 20, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33.0), +@@ -571,6 +589,7 @@ func TestGcPacer(t *testing.T) { + name: "MaintainMemoryLimitNoGCPercent", + gcPercent: -1, + memoryLimit: 512 << 20, ++ gcRatio: 0.25, + globalsBytes: 32 << 10, + nCores: 8, + allocRate: constant(33.0), +@@ -607,7 +626,7 @@ func TestGcPacer(t *testing.T) { + t.Run(e.name, func(t *testing.T) { + t.Parallel() + +- c := NewGCController(e.gcPercent, e.memoryLimit) ++ c := NewGCController(e.gcPercent, e.memoryLimit, e.gcRatio) + var bytesAllocatedBlackLast int64 + results := make([]gcCycleResult, 0, e.length) + for i := 0; i < e.length; i++ { +@@ -762,6 +781,7 @@ type gcExecTest struct { + + gcPercent int + memoryLimit int64 ++ gcRatio float64 + globalsBytes uint64 + nCores int + +@@ -1034,7 +1054,7 @@ func applyMemoryLimitHeapGoalHeadroom(goal uint64) uint64 { + + func TestIdleMarkWorkerCount(t *testing.T) { + const workers = 10 +- c := NewGCController(100, math.MaxInt64) ++ c := NewGCController(100, math.MaxInt64, 0.25) + c.SetMaxIdleMarkWorkers(workers) + for i := 0; i < workers; i++ { + if !c.NeedIdleMarkWorker() { +-- +2.33.0 + |