Skip to content

Commit f53f718

Browse files
committed
sweet: add mode for PGO benchmarking
With -pgo, for each configuration sweet automatically runs an initial profiling configuration. The merged profiles from these runs are used as the PGO input to a ".pgo" variant of the configuration. Comparing the base configuration to the ".pgo" variant indicates the effect of PGO. At a lower level, the config format adds a "pgofiles" map, which can be used to specify PGO profiles to use for each benchmark. Ultimately this sets GOFLAGS=-pgo=/path in BuildEnv. Some benchmarks may not currently properly plumb this into their build (e.g., the GoBuild benchmarks don't build the compiler at all). Existing benchmarks need to be double-checked that they actually get PGO enabled. For golang/go#55022. Change-Id: I81c0cb085ef3b5196a05d2565dd0e2f83057b9fa
1 parent 5a591f8 commit f53f718

File tree

9 files changed

+252
-58
lines changed

9 files changed

+252
-58
lines changed

sweet/benchmarks/go-build/main.go

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"golang.org/x/benchmarks/sweet/benchmarks/internal/cgroups"
1818
"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
1919
"golang.org/x/benchmarks/sweet/common"
20+
sprofile "golang.org/x/benchmarks/sweet/common/profile"
2021
)
2122

2223
var (
@@ -131,15 +132,20 @@ func run(pkgPath string) error {
131132
}
132133

133134
func mergeProfiles(dir, prefix string) (*profile.Profile, error) {
134-
profiles, err := collectProfiles(dir, prefix)
135+
profiles, err := sprofile.ReadDir(dir, func(name string) bool {
136+
return strings.HasPrefix(name, prefix)
137+
})
135138
if err != nil {
136139
return nil, err
137140
}
138141
return profile.Merge(profiles)
139142
}
140143

141144
func copyProfiles(dir, bin string, typ driver.ProfileType, finalPrefix string) error {
142-
profiles, err := collectProfiles(dir, profilePrefix(bin, typ))
145+
prefix := profilePrefix(bin, typ)
146+
profiles, err := sprofile.ReadDir(dir, func(name string) bool {
147+
return strings.HasPrefix(name, prefix)
148+
})
143149
if err != nil {
144150
return err
145151
}
@@ -151,34 +157,6 @@ func copyProfiles(dir, bin string, typ driver.ProfileType, finalPrefix string) e
151157
return nil
152158
}
153159

154-
func collectProfiles(dir, prefix string) ([]*profile.Profile, error) {
155-
entries, err := os.ReadDir(dir)
156-
if err != nil {
157-
return nil, err
158-
}
159-
var profiles []*profile.Profile
160-
for _, entry := range entries {
161-
name := entry.Name()
162-
path := filepath.Join(tmpDir, name)
163-
if info, err := entry.Info(); err != nil {
164-
return nil, err
165-
} else if info.Size() == 0 {
166-
// Skip zero-sized files, otherwise the pprof package
167-
// will call it a parsing error.
168-
continue
169-
}
170-
if strings.HasPrefix(name, prefix) {
171-
p, err := driver.ReadProfile(path)
172-
if err != nil {
173-
return nil, err
174-
}
175-
profiles = append(profiles, p)
176-
continue
177-
}
178-
}
179-
return profiles, nil
180-
}
181-
182160
func profilePrefix(bin string, typ driver.ProfileType) string {
183161
return bin + "-prof." + string(typ)
184162
}

sweet/benchmarks/internal/cgroups/cgroups.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,3 @@ func (c *Cmd) RSSFunc() func() (uint64, error) {
110110
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
111111
}
112112
}
113-

sweet/benchmarks/internal/driver/driver.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -547,15 +547,6 @@ func ProfilingEnabled(typ ProfileType) bool {
547547
panic("bad profile type")
548548
}
549549

550-
func ReadProfile(filename string) (*profile.Profile, error) {
551-
f, err := os.Open(filename)
552-
if err != nil {
553-
return nil, err
554-
}
555-
defer f.Close()
556-
return profile.Parse(f)
557-
}
558-
559550
func WriteProfile(prof *profile.Profile, typ ProfileType, pattern string) error {
560551
if !ProfilingEnabled(typ) {
561552
return fmt.Errorf("this type of profile is not currently enabled")

sweet/benchmarks/tile38/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
2424
"golang.org/x/benchmarks/sweet/benchmarks/internal/pool"
25+
"golang.org/x/benchmarks/sweet/common/profile"
2526

2627
"github.com/gomodule/redigo/redis"
2728
)
@@ -307,7 +308,7 @@ func runOne(bench benchmark, cfg *config) (err error) {
307308
// Copy it over.
308309
for _, typ := range []driver.ProfileType{driver.ProfileCPU, driver.ProfileMem} {
309310
if driver.ProfilingEnabled(typ) {
310-
p, r := driver.ReadProfile(cfg.profilePath(typ))
311+
p, r := profile.Read(cfg.profilePath(typ))
311312
if r != nil {
312313
err = r
313314
return

sweet/cmd/sweet/benchmark.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,20 +202,27 @@ func (b *benchmark) execute(cfgs []*common.Config, r *runCfg) error {
202202
return err
203203
}
204204

205-
// Retrieve the benchmark's source.
206-
if err := b.harness.Get(srcDir); err != nil {
207-
return fmt.Errorf("retrieving source for %s: %v", b.name, err)
205+
// Retrieve the benchmark's source, if needed. If execute is called
206+
// multiple times, this will already be done.
207+
_, err := os.Stat(srcDir)
208+
if os.IsNotExist(err) {
209+
if err := b.harness.Get(srcDir); err != nil {
210+
return fmt.Errorf("retrieving source for %s: %v", b.name, err)
211+
}
208212
}
209213

210214
// Create the results directory for the benchmark.
211-
resultsDir := filepath.Join(r.resultsDir, b.name)
215+
resultsDir := r.benchmarkResultsDir(b)
212216
if err := mkdirAll(resultsDir); err != nil {
213217
return fmt.Errorf("creating results directory for %s: %v", b.name, err)
214218
}
215219

216220
// Perform a setup step for each config for the benchmark.
217221
setups := make([]common.RunConfig, 0, len(cfgs))
218-
for _, cfg := range cfgs {
222+
for _, pcfg := range cfgs {
223+
// Local copy for per-benchmark environment adjustments.
224+
cfg := pcfg.Copy()
225+
219226
// Create directory hierarchy for benchmarks.
220227
workDir := filepath.Join(topDir, cfg.Name)
221228
binDir := filepath.Join(workDir, "bin")
@@ -236,6 +243,16 @@ func (b *benchmark) execute(cfgs []*common.Config, r *runCfg) error {
236243
}
237244
}
238245

246+
// Add PGO if profile specified for this benchmark.
247+
if pgo, ok := cfg.PGOFiles[b.name]; ok {
248+
goflags, ok := cfg.BuildEnv.Lookup("GOFLAGS")
249+
if ok {
250+
goflags += " "
251+
}
252+
goflags += fmt.Sprintf("-pgo=%s", pgo)
253+
cfg.BuildEnv.Env = cfg.BuildEnv.MustSet("GOFLAGS=" + goflags)
254+
}
255+
239256
// Build the benchmark (application and any other necessary components).
240257
bcfg := common.BuildConfig{
241258
BinDir: binDir,
@@ -264,7 +281,7 @@ func (b *benchmark) execute(cfgs []*common.Config, r *runCfg) error {
264281
}
265282
if r.cpuProfile || r.memProfile || r.perf {
266283
// Create a directory for any profile files to live in.
267-
resultsProfilesDir := filepath.Join(resultsDir, fmt.Sprintf("%s.debug", cfg.Name))
284+
resultsProfilesDir := r.runProfilesDir(b, cfg)
268285
mkdirAll(resultsProfilesDir)
269286

270287
// We need to pass arguments to the benchmark binary to generate

sweet/cmd/sweet/integration_test.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ import (
2020
)
2121

2222
func TestSweetEndToEnd(t *testing.T) {
23+
t.Run("standard", func(t *testing.T) {
24+
testSweetEndToEnd(t, false)
25+
})
26+
t.Run("pgo", func(t *testing.T) {
27+
testSweetEndToEnd(t, true)
28+
})
29+
}
30+
31+
func testSweetEndToEnd(t *testing.T, pgo bool) {
2332
if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
2433
t.Skip("Sweet is currently only fully supported on linux/amd64")
2534
}
@@ -39,6 +48,17 @@ func TestSweetEndToEnd(t *testing.T) {
3948
Env: common.NewEnvFromEnviron(),
4049
}
4150

51+
if pgo {
52+
cmd := exec.Command(goTool.Tool, "help", "build")
53+
out, err := cmd.Output()
54+
if err != nil {
55+
t.Fatalf("error running go help build: %v", err)
56+
}
57+
if !strings.Contains(string(out), "-pgo") {
58+
t.Skip("toolchain missing -pgo support")
59+
}
60+
}
61+
4262
// Build sweet.
4363
wd, err := os.Getwd()
4464
if err != nil {
@@ -103,7 +123,8 @@ func TestSweetEndToEnd(t *testing.T) {
103123

104124
var outputMu sync.Mutex
105125
runShard := func(shard, resultsDir, workDir string) {
106-
runCmd := exec.Command(sweetBin, "run",
126+
args := []string{
127+
"run",
107128
"-run", shard,
108129
"-shell",
109130
"-count", "1",
@@ -112,8 +133,12 @@ func TestSweetEndToEnd(t *testing.T) {
112133
"-results", resultsDir,
113134
"-work-dir", workDir,
114135
"-short",
115-
cfgPath,
116-
)
136+
}
137+
if pgo {
138+
args = append(args, "-pgo")
139+
}
140+
args = append(args, cfgPath)
141+
runCmd := exec.Command(sweetBin, args...)
117142
output, runErr := runCmd.CombinedOutput()
118143

119144
outputMu.Lock()

0 commit comments

Comments
 (0)