Introduction
A one-time profile is useful, but PGO shines when integrated into your CI/CD pipeline. By automatically collecting profiles and rebuilding with PGO, you ensure every release benefits from the latest production behavior. This lesson covers practical workflows for collecting, merging, and maintaining PGO profiles.
Key Concepts
- Profile merging: Combining multiple pprof profiles into one using
go tool pprof -proto. This produces a more representative profile. - Iterative PGO: Each release's profile feeds the next build. Over 2-3 iterations, PGO converges to maximum effectiveness.
- Profile representativeness: The profile must cover your typical production workload. A profile from a quiet period won't optimize peak-load paths.
Real World Context
At scale, teams collect profiles from multiple production instances (to avoid single-instance bias), merge them, and feed the merged profile to CI. GitHub Actions, GitLab CI, and other systems can automate this: collect weekly, commit to repo, rebuild, deploy.
Deep Dive
Collecting Representative Profiles
Collect from multiple instances and time periods:
bash# Collect from 3 production instances curl -o prof1.pgo http://prod1:6060/debug/pprof/profile?seconds=30 curl -o prof2.pgo http://prod2:6060/debug/pprof/profile?seconds=30 curl -o prof3.pgo http://prod3:6060/debug/pprof/profile?seconds=30 # Merge into a single representative profile go tool pprof -proto prof1.pgo prof2.pgo prof3.pgo > default.pgo
CI Pipeline Integration
A typical CI workflow:
yaml# .github/workflows/build.yml steps: - name: Build with PGO run: | # default.pgo is committed to the repo go build -o myapp ./cmd/myapp - name: Refresh profile (weekly job) if: github.event.schedule run: | curl -o default.pgo http://prod:6060/debug/pprof/profile?seconds=60 git add default.pgo git commit -m "chore: refresh PGO profile"
Iterative Improvement
PGO builds can further optimize on subsequent iterations:
- Build v1 without PGO → deploy
- Collect profile from v1 → build v2 with PGO → deploy
- Collect profile from v2 → build v3 with PGO → deploy
By v3, the compiler has optimized code that was already optimized — the gains converge, typically within 2-3 iterations.
Benchmarking PGO Impact
bash# Build without PGO go build -pgo=off -o myapp-nopgo ./cmd/myapp # Build with PGO go build -o myapp-pgo ./cmd/myapp # Compare with your benchmark suite hyperfine './myapp-nopgo serve' './myapp-pgo serve'
Common Pitfalls
- Single-instance profiles — One instance may have unusual traffic. Always merge profiles from multiple instances.
- Not re-collecting after major refactors — A profile pointing to renamed or removed functions is wasted. Refresh after major changes.
Best Practices
- Commit default.pgo to your repository — Every developer and CI build automatically uses PGO.
- Set up automated weekly profile refresh — A cron-triggered CI job keeps the profile current without manual intervention.
Summary
- Merge profiles from multiple instances for representative coverage.
- Integrate PGO into CI: commit
default.pgo, rebuild automatically. - PGO converges over 2-3 iterations — each release improves on the last.
- Use
-pgo=offbuilds as baseline to measure PGO impact. - Automate profile refresh with scheduled CI jobs.
Code Examples
# Merge profiles from multiple production instances
go tool pprof -proto prof1.pgo prof2.pgo prof3.pgo > default.pgo
# Build with explicit PGO control
go build -pgo=default.pgo -o myapp ./cmd/myapp # Use specific profile
go build -pgo=off -o myapp ./cmd/myapp # Disable PGO for baseline
go build -o myapp ./cmd/myapp # Auto-detect default.pgo