Skip to content

Commit dc0c437

Browse files
EtiennePerotgvisor-bot
authored andcommitted
PGO: Run benchmarks for a shorter time, but force GC to run more often.
The PGO profiles don't need to run that long, but they do need to ensure GC happens a few times to exercise GC-triggered codepaths (e.g. on `sync.Pool` objects, network packet chunk pools, etc). This change introduces a flag to force GC to run on a set interval during CPU profiling, and enables it for PGO benchmark runs. It also reduces the PGO benchmark time target, since GC is now guaranteed to run a couple of times. It also deletes all profiles, in order to force them to be refreshed with this change in effect. PiperOrigin-RevId: 797835198
1 parent b4259c3 commit dc0c437

File tree

10 files changed

+83
-22
lines changed

10 files changed

+83
-22
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ run-benchmark: load-benchmarks ## Runs single benchmark and optionally sends dat
548548
# The arguments passed to benchmarks when run for PGO profile collection.
549549
# This should *not* include the `-profile` or `-profile-cpu` arguments, as
550550
# those are added automatically.
551-
BENCHMARKS_ARGS_PGO := -test.v -test.bench=. -test.benchtime=30s
551+
BENCHMARKS_ARGS_PGO := -test.v -test.bench=. -test.benchtime=20s
552552
# The threshold below which the `benchmark-refresh-pgo` rule will update the
553553
# profile.
554554
BENCHMARKS_PGO_REFRESH_THRESHOLD ?= 0.7
@@ -590,7 +590,7 @@ benchmark-refresh-pgo: load-benchmarks $(RUNTIME_BIN) ## Refresh profiles of all
590590
fi; \
591591
mkdir -p "$$(dirname "$${PGO_PROFILE_OLD}")"; \
592592
mkdir -p "$${PLATFORM_TMPDIR}/$${PGO_BENCHMARK_BASENAME}"; \
593-
$(call install_runtime,$${PLATFORM}_$${PGO_RUNTIME_KEY}_pgo_$${PGO_BENCHMARK_BASENAME},--platform $${PLATFORM} --profile --profile-cpu="$${PLATFORM_TMPDIR}/$${PGO_BENCHMARK_BASENAME}/$${PGO_BENCHMARK_BASENAME}.%YYYY%-%MM%-%DD%_%HH%-%II%-%SS%-%NN%.pgo.pprof.pb.gz"); \
593+
$(call install_runtime,$${PLATFORM}_$${PGO_RUNTIME_KEY}_pgo_$${PGO_BENCHMARK_BASENAME},--platform $${PLATFORM} --profile --profile-gc-interval=3s --profile-cpu="$${PLATFORM_TMPDIR}/$${PGO_BENCHMARK_BASENAME}/$${PGO_BENCHMARK_BASENAME}.%YYYY%-%MM%-%DD%_%HH%-%II%-%SS%-%NN%.pgo.pprof.pb.gz"); \
594594
$(call sudo,$${PGO_BENCHMARK_TARGET},-runtime=$${PLATFORM}_$${PGO_RUNTIME_KEY}_pgo_$${PGO_BENCHMARK_BASENAME} -pgo-benchmarks=true $(BENCHMARKS_ARGS_PGO)); \
595595
$(call run,tools/profiletool,merge --out="$${PGO_PROFILE_NEW}" "$${PLATFORM_TMPDIR}/$${PGO_BENCHMARK_BASENAME}"); \
596596
rm -rf --one-file-system "$${PLATFORM_TMPDIR}/$${PGO_BENCHMARK_BASENAME}"; \

runsc/cmd/boot.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
523523
ProductName: b.productName,
524524
PodInitConfigFD: b.podInitConfigFD,
525525
SinkFDs: b.sinkFDs.GetArray(),
526-
ProfileOpts: b.profileFDs.ToOpts(),
526+
ProfileOpts: profile.MakeOpts(&b.profileFDs, conf.ProfileGCInterval),
527527
NvidiaDriverVersion: nvidiaDriverVersion,
528528
HostTHP: b.hostTHP,
529529
SaveFDs: b.saveFDs.GetFDs(),

runsc/cmd/gofer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomm
201201
}
202202

203203
// Start profiling. This will be a noop if no profiling arguments were passed.
204-
profileOpts := g.profileFDs.ToOpts()
204+
profileOpts := profile.MakeOpts(&g.profileFDs, conf.ProfileGCInterval)
205205
g.stopProfiling = profile.Start(profileOpts)
206206

207207
// At this point we won't re-execute, so it's safe to limit via rlimits. Any
@@ -278,7 +278,7 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomm
278278
opts := filter.Options{
279279
UDSOpenEnabled: conf.GetHostUDS().AllowOpen(),
280280
UDSCreateEnabled: conf.GetHostUDS().AllowCreate(),
281-
ProfileEnabled: len(profileOpts) > 0,
281+
ProfileEnabled: profileOpts.Enabled(),
282282
DirectFS: conf.DirectFS,
283283
CgoEnabled: config.CgoEnabled,
284284
}

runsc/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"runtime"
2525
"strconv"
2626
"strings"
27+
"time"
2728

2829
"gvisor.dev/gvisor/pkg/log"
2930
"gvisor.dev/gvisor/pkg/refs"
@@ -230,6 +231,15 @@ type Config struct {
230231
// duration of the container execution. Requires ProfileEnabled.
231232
ProfileCPU string `flag:"profile-cpu"`
232233

234+
// ProfileGCInterval forces a garbage-collection cycle at regular intervals.
235+
// Useful for benchmark runs where the GC threshold may not be reached
236+
// but for which it is useful to have GC (and the effects of GC) show
237+
// up in the traces, while keeping the profile short.
238+
// Requires `ProfileEnable`, and triggered when any other type of
239+
// time-based profiling is enabled.
240+
// If zero, GC happens per the Go runtime's default behavior.
241+
ProfileGCInterval time.Duration `flag:"profile-gc-interval"`
242+
233243
// ProfileHeap collects a heap profile to the passed file for the
234244
// duration of the container execution. Requires ProfileEnabled.
235245
ProfileHeap string `flag:"profile-heap"`

runsc/config/flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func RegisterFlags(flagSet *flag.FlagSet) {
9999
flagSet.Bool("profile", false, "prepares the sandbox to use Golang profiler. Note that enabling profiler loosens the seccomp protection added to the sandbox (DO NOT USE IN PRODUCTION).")
100100
flagSet.String("profile-block", "", "collects a block profile to this file path for the duration of the container execution. Requires -profile=true.")
101101
flagSet.String("profile-cpu", "", "collects a CPU profile to this file path for the duration of the container execution. Requires -profile=true.")
102+
flagSet.Duration("profile-gc-interval", 0, "forces a garbage collection cycle every this duration while another type of time-based profiling is enabled. Requires -profile=true.")
102103
flagSet.String("profile-heap", "", "collects a heap profile to this file path for the duration of the container execution. Requires -profile=true.")
103104
flagSet.String("profile-mutex", "", "collects a mutex profile to this file path for the duration of the container execution. Requires -profile=true.")
104105
flagSet.String("trace", "", "collects a Go runtime execution trace to this file path for the duration of the container execution.")

runsc/profile/profile.go

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -77,26 +77,46 @@ func (fds *FDArgs) SetFromFlags(f *flag.FlagSet) {
7777
f.IntVar(&fds.TraceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.")
7878
}
7979

80-
// Opts is a map of profile Kind to FD.
81-
type Opts map[Kind]uintptr
80+
// Opts is a struct that holds the options for profiling.
81+
type Opts struct {
82+
// FDs is a map of profile Kind to FD.
83+
FDs map[Kind]uintptr
8284

83-
// ToOpts turns FDArgs into an Opts struct which can be passed to Start.
84-
func (fds *FDArgs) ToOpts() Opts {
85-
o := Opts{}
85+
// GCInterval is the interval at which to force a garbage collection cycle.
86+
// If zero, GC happens per the Go runtime's default behavior.
87+
GCInterval time.Duration
88+
}
89+
90+
// Enabled returns true if any profile type is enabled.
91+
func (opts Opts) Enabled() bool {
92+
for _, fd := range opts.FDs {
93+
if fd != 0 {
94+
return true
95+
}
96+
}
97+
return false
98+
}
99+
100+
// MakeOpts creates an Opts struct from the given FDArgs and GC interval.
101+
func MakeOpts(fds *FDArgs, gcInterval time.Duration) Opts {
102+
o := Opts{
103+
FDs: make(map[Kind]uintptr),
104+
GCInterval: gcInterval,
105+
}
86106
if fds.BlockFD >= 0 {
87-
o[Block] = uintptr(fds.BlockFD)
107+
o.FDs[Block] = uintptr(fds.BlockFD)
88108
}
89109
if fds.CPUFD >= 0 {
90-
o[CPU] = uintptr(fds.CPUFD)
110+
o.FDs[CPU] = uintptr(fds.CPUFD)
91111
}
92112
if fds.HeapFD >= 0 {
93-
o[Heap] = uintptr(fds.HeapFD)
113+
o.FDs[Heap] = uintptr(fds.HeapFD)
94114
}
95115
if fds.MutexFD >= 0 {
96-
o[Mutex] = uintptr(fds.MutexFD)
116+
o.FDs[Mutex] = uintptr(fds.MutexFD)
97117
}
98118
if fds.TraceFD >= 0 {
99-
o[Trace] = uintptr(fds.TraceFD)
119+
o.FDs[Trace] = uintptr(fds.TraceFD)
100120
}
101121
return o
102122
}
@@ -112,11 +132,41 @@ func Start(opts Opts) func() {
112132
}
113133
}
114134

115-
if fd, ok := opts[Block]; ok {
135+
var stopPeriodicGC chan struct{}
136+
maybeEnablePeriodicGC := func() {
137+
if opts.GCInterval > 0 && stopPeriodicGC == nil {
138+
log.Infof("Periodic garbage collection enabled during profiling; will run GC every %v.", opts.GCInterval)
139+
stopPeriodicGC = make(chan struct{}, 1)
140+
stoppedCh := make(chan struct{}, 1)
141+
go func() {
142+
ticker := time.NewTicker(opts.GCInterval)
143+
defer ticker.Stop()
144+
for {
145+
select {
146+
case <-stopPeriodicGC:
147+
stoppedCh <- struct{}{}
148+
close(stoppedCh)
149+
return
150+
case <-ticker.C:
151+
log.Debugf("Forcing garbage collection per profile options")
152+
runtime.GC()
153+
}
154+
}
155+
}()
156+
onStopProfiling = append(onStopProfiling, func() {
157+
stopPeriodicGC <- struct{}{}
158+
close(stopPeriodicGC)
159+
<-stoppedCh // Wait for the goroutine to exit; this ensures periodic GC is not going to run further.
160+
})
161+
}
162+
}
163+
164+
if fd, ok := opts.FDs[Block]; ok {
116165
log.Infof("Block profiling enabled")
117166
file := os.NewFile(fd, "profile-block")
118167

119168
runtime.SetBlockProfileRate(control.DefaultBlockProfileRate)
169+
maybeEnablePeriodicGC()
120170
onStopProfiling = append(onStopProfiling, func() {
121171
if err := pprof.Lookup("block").WriteTo(file, 0); err != nil {
122172
log.Warningf("Error writing block profile: %v", err)
@@ -127,19 +177,20 @@ func Start(opts Opts) func() {
127177
})
128178
}
129179

130-
if fd, ok := opts[CPU]; ok {
180+
if fd, ok := opts.FDs[CPU]; ok {
131181
log.Infof("CPU profiling enabled")
132182
file := os.NewFile(fd, "profile-cpu")
133183

134184
pprof.StartCPUProfile(file)
185+
maybeEnablePeriodicGC()
135186
onStopProfiling = append(onStopProfiling, func() {
136187
pprof.StopCPUProfile()
137188
file.Close()
138189
log.Infof("CPU profiling stopped")
139190
})
140191
}
141192

142-
if fd, ok := opts[Heap]; ok {
193+
if fd, ok := opts.FDs[Heap]; ok {
143194
log.Infof("Heap profiling enabled")
144195
file := os.NewFile(fd, "profile-heap")
145196

@@ -152,11 +203,12 @@ func Start(opts Opts) func() {
152203
})
153204
}
154205

155-
if fd, ok := opts[Mutex]; ok {
206+
if fd, ok := opts.FDs[Mutex]; ok {
156207
log.Infof("Mutex profiling enabled")
157208
file := os.NewFile(fd, "profile-mutex")
158209

159210
prev := runtime.SetMutexProfileFraction(control.DefaultMutexProfileRate)
211+
maybeEnablePeriodicGC()
160212
onStopProfiling = append(onStopProfiling, func() {
161213
if err := pprof.Lookup("mutex").WriteTo(file, 0); err != nil {
162214
log.Warningf("Error writing mutex profile: %v", err)
@@ -167,7 +219,7 @@ func Start(opts Opts) func() {
167219
})
168220
}
169221

170-
if fd, ok := opts[Trace]; ok {
222+
if fd, ok := opts.FDs[Trace]; ok {
171223
log.Infof("Tracing enabled")
172224
file := os.NewFile(fd, "trace")
173225

runsc/profiles/data/go1.24.1-linux-amd64_kvm/ffmpeg.pgo.pkg_commithash

Lines changed: 0 additions & 1 deletion
This file was deleted.
Binary file not shown.

runsc/profiles/data/go1.24.1-linux-amd64_systrap/ffmpeg.pgo.pkg_commithash

Lines changed: 0 additions & 1 deletion
This file was deleted.
Binary file not shown.

0 commit comments

Comments
 (0)