Skip to content

Commit 86fe6c6

Browse files
author
Raj Barik
committed
[WIP]Profile-guided optimization for Golang v1.17
1 parent b954f58 commit 86fe6c6

14 files changed

+7992
-26
lines changed

src/cmd/compile/internal/base/flag.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,13 @@ type CmdFlags struct {
121121
TraceProfile string "help:\"write an execution trace to `file`\""
122122
TrimPath string "help:\"remove `prefix` from recorded source file paths\""
123123
WB bool "help:\"enable write barrier\"" // TODO: remove
124-
124+
FpprofProfileUse string "help:\"read profile from `file`\""
125+
WBudget bool "help:\"Inline using caller budget\""
126+
PprofInline bool "help:\"perform inlining based on profile\""
127+
CodeLayout bool "help:\"perform code layout optimization based on profile\""
128+
Devirtualize bool "help:\"preform code type specialization based on profile\""
129+
Specialize bool "help:\"preform code type specialization based on profile\""
130+
CsInline bool "help:\"perform context-sensitive inlining based on profile\""
125131
// Configuration derived from flags; not a flag itself.
126132
Cfg struct {
127133
Embed struct { // set by -embedcfg

src/cmd/compile/internal/gc/main.go

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"cmd/compile/internal/ir"
1717
"cmd/compile/internal/logopt"
1818
"cmd/compile/internal/noder"
19+
"cmd/compile/internal/pgo"
1920
"cmd/compile/internal/pkginit"
2021
"cmd/compile/internal/reflectdata"
2122
"cmd/compile/internal/ssa"
@@ -223,10 +224,32 @@ func Main(archInit func(*ssagen.ArchInfo)) {
223224
typecheck.AllImportedBodies()
224225
}
225226

227+
// Pprofprofileuse
228+
base.Timer.Start("fe", "pprofprofileuse")
229+
if base.Flag.FpprofProfileUse != "" {
230+
pgo.WeightedCG = pgo.WeightedCallGraph(base.Flag.FpprofProfileUse, &pgo.Options{
231+
CallTree: false,
232+
SampleValue: func(v []int64) int64 { return v[1] },
233+
})
234+
}
235+
236+
// Specializer
237+
base.Timer.Start("fe", "specialize")
238+
if pgo.WeightedCG != nil && base.Flag.Specialize {
239+
pgo.Specializer(pgo.WeightedCG)
240+
}
241+
226242
// Inlining
227243
base.Timer.Start("fe", "inlining")
228244
if base.Flag.LowerL != 0 {
229-
inline.InlinePackage()
245+
InliningDone := false
246+
if base.Flag.FpprofProfileUse != "" && pgo.WeightedCG != nil && base.Flag.PprofInline {
247+
pgo.PprofInline()
248+
InliningDone = true
249+
}
250+
if InliningDone == false {
251+
inline.InlinePackage()
252+
}
230253
}
231254

232255
// Devirtualize.
@@ -251,7 +274,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
251274
// because large values may contain pointers, it must happen early.
252275
base.Timer.Start("fe", "escapes")
253276
escape.Funcs(typecheck.Target.Decls)
254-
255277
// Collect information for go:nowritebarrierrec
256278
// checking. This must happen before transforming closures during Walk
257279
// We'll do the final check after write barriers are
@@ -260,6 +282,26 @@ func Main(archInit func(*ssagen.ArchInfo)) {
260282
ssagen.EnableNoWriteBarrierRecCheck()
261283
}
262284

285+
// CodeLayout optimization based on profiles.
286+
base.Timer.Start("fe", "codelayout")
287+
if base.Flag.CodeLayout && base.Flag.FpprofProfileUse != "" {
288+
if pgo.WeightedCG == nil {
289+
pgo.WeightedCG = pgo.WeightedCallGraph(base.Flag.FpprofProfileUse, &pgo.Options{
290+
CallTree: false,
291+
SampleValue: func(v []int64) int64 { return v[1] },
292+
})
293+
}
294+
if pgo.WeightedCG != nil {
295+
pgo.FuncList = make(map[*ir.Func]struct{})
296+
for i := 0; i < len(typecheck.Target.Decls); i++ {
297+
if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
298+
pgo.AddFunc(fn)
299+
}
300+
}
301+
pgo.PerformCodeLayout()
302+
}
303+
}
304+
263305
// Prepare for SSA compilation.
264306
// This must be before CompileITabs, because CompileITabs
265307
// can trigger function compilation.
@@ -290,7 +332,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
290332
// Write barriers are now known. Check the call graph.
291333
ssagen.NoWriteBarrierRecCheck()
292334
}
293-
294335
// Finalize DWARF inline routine DIEs, then explicitly turn off
295336
// DWARF inlining gen so as to avoid problems with generated
296337
// method wrappers.
@@ -299,7 +340,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
299340
base.Ctxt.DwFixups = nil
300341
base.Flag.GenDwarfInl = 0
301342
}
302-
303343
// Write object data to disk.
304344
base.Timer.Start("be", "dumpobj")
305345
dumpdata()
@@ -308,17 +348,14 @@ func Main(archInit func(*ssagen.ArchInfo)) {
308348
if base.Flag.AsmHdr != "" {
309349
dumpasmhdr()
310350
}
311-
312351
ssagen.CheckLargeStacks()
313352
typecheck.CheckFuncStack()
314-
315353
if len(compilequeue) != 0 {
316354
base.Fatalf("%d uncompiled functions", len(compilequeue))
317355
}
318356

319357
logopt.FlushLoggedOpts(base.Ctxt, base.Ctxt.Pkgpath)
320358
base.ExitIfErrors()
321-
322359
base.FlushErrors()
323360
base.Timer.Stop()
324361

src/cmd/compile/internal/inline/inl.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,28 @@
2727
package inline
2828

2929
import (
30-
"fmt"
31-
"go/constant"
32-
"strings"
33-
3430
"cmd/compile/internal/base"
3531
"cmd/compile/internal/ir"
3632
"cmd/compile/internal/logopt"
3733
"cmd/compile/internal/typecheck"
3834
"cmd/compile/internal/types"
3935
"cmd/internal/obj"
4036
"cmd/internal/src"
37+
"fmt"
38+
"go/constant"
39+
"strings"
4140
)
4241

4342
// Inlining budget parameters, gathered in one place
4443
const (
45-
inlineMaxBudget = 80
46-
inlineExtraAppendCost = 0
44+
inlineMaxBudget = 80
45+
profileinlineMaxBudget = 1000
46+
Threshold = float64(2)
47+
inlineExtraAppendCost = 0
4748
// default is to inline if there's at most one call. -l=4 overrides this by using 1 instead.
48-
inlineExtraCallCost = 57 // 57 was benchmarked to provided most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742
49-
inlineExtraPanicCost = 1 // do not penalize inlining panics.
50-
inlineExtraThrowCost = inlineMaxBudget // with current (2018-05/1.11) code, inlining runtime.throw does not help.
49+
inlineExtraCallCost = 57 // 57 was benchmarked to provided most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742
50+
inlineExtraPanicCost = 1 // do not penalize inlining panics.
51+
inlineExtraThrowCost = 1000 // with current (2018-05/1.11) code, inlining runtime.throw does not help.
5152

5253
inlineBigFunctionNodes = 5000 // Functions with this many nodes are considered "big".
5354
inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function.
@@ -62,7 +63,7 @@ func InlinePackage() {
6263
// We allow inlining if there is no
6364
// recursion, or the recursion cycle is
6465
// across more than one function.
65-
CanInline(n)
66+
CanInline(n, int32(inlineMaxBudget))
6667
} else {
6768
if base.Flag.LowerM > 1 {
6869
fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(n), n.Nname)
@@ -76,11 +77,10 @@ func InlinePackage() {
7677
// CanInline determines whether fn is inlineable.
7778
// If so, CanInline saves copies of fn.Body and fn.Dcl in fn.Inl.
7879
// fn and fn.Body will already have been typechecked.
79-
func CanInline(fn *ir.Func) {
80+
func CanInline(fn *ir.Func, maxBudget int32) {
8081
if fn.Nname == nil {
8182
base.Fatalf("CanInline no nname %+v", fn)
8283
}
83-
8484
var reason string // reason, if any, that the function was not inlined
8585
if base.Flag.LowerM > 1 || logopt.Enabled() {
8686
defer func() {
@@ -167,16 +167,16 @@ func CanInline(fn *ir.Func) {
167167
// list. See issue 25249 for more context.
168168

169169
visitor := hairyVisitor{
170-
budget: inlineMaxBudget,
170+
budget: maxBudget,
171171
extraCallCost: cc,
172172
}
173-
if visitor.tooHairy(fn) {
173+
if visitor.tooHairy(fn, maxBudget) {
174174
reason = visitor.reason
175175
return
176176
}
177177

178178
n.Func.Inl = &ir.Inline{
179-
Cost: inlineMaxBudget - visitor.budget,
179+
Cost: maxBudget - visitor.budget,
180180
Dcl: pruneUnusedAutos(n.Defn.(*ir.Func).Dcl, &visitor),
181181
Body: inlcopylist(fn.Body),
182182
}
@@ -257,13 +257,13 @@ type hairyVisitor struct {
257257
do func(ir.Node) bool
258258
}
259259

260-
func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
260+
func (v *hairyVisitor) tooHairy(fn *ir.Func, maxBudget int32) bool {
261261
v.do = v.doNode // cache closure
262262
if ir.DoChildren(fn, v.do) {
263263
return true
264264
}
265265
if v.budget < 0 {
266-
v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-v.budget, inlineMaxBudget)
266+
v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", maxBudget-v.budget, maxBudget)
267267
return true
268268
}
269269
return false
@@ -711,7 +711,7 @@ func inlCallee(fn ir.Node) *ir.Func {
711711
case ir.OCLOSURE:
712712
fn := fn.(*ir.ClosureExpr)
713713
c := fn.Func
714-
CanInline(c)
714+
CanInline(c, int32(inlineMaxBudget))
715715
return c
716716
}
717717
return nil
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package pgo
2+
3+
import (
4+
"cmd/compile/internal/base"
5+
"cmd/compile/internal/ir"
6+
"log"
7+
"os"
8+
)
9+
10+
var (
11+
// WeightedCG represents the IrGraph built from pprof profile, which we will update as part of inlining.
12+
WeightedCG *IrGraph
13+
// Aggrehated Node and EdgeWeights across the entore IrGraph.
14+
TotalNodeWeight = int64(0)
15+
TotalEdgeWeight = int64(0)
16+
)
17+
18+
// WeightedCallGraph generates a callgraph from the pprof profile.
19+
func WeightedCallGraph(profileFile string, opt *Options) *IrGraph {
20+
// open the pprof profile file
21+
f, err := os.Open(profileFile)
22+
if err != nil {
23+
log.Fatal("WeightedCG: failed to open file " + profileFile)
24+
return nil
25+
}
26+
defer f.Close()
27+
p, err := Parse(f)
28+
if err != nil {
29+
log.Fatal("WeightedCG: Failed to Parse profile file.")
30+
return nil
31+
}
32+
g := New(p, opt)
33+
if g != nil && base.Flag.LowerM > 1 {
34+
log.Println("WeightedCG: Pprof callgraph created successfully")
35+
}
36+
// Compute TotalNodeWeight and EdgeWeights across all nodes in the pprof graph.
37+
for _, n := range g.Nodes {
38+
TotalNodeWeight += n.FlatValue()
39+
for _, e := range n.Out {
40+
TotalEdgeWeight += e.WeightValue()
41+
}
42+
}
43+
// Create IrGraph nodes from PProf.
44+
NodeMap := CreateIRGraphNodes(g)
45+
// Create the WeightCallgraph from PProf.
46+
WeightedCG := CreateIRGraph(NodeMap)
47+
if WeightedCG != nil && base.Flag.LowerM > 1 {
48+
log.Println("WeightedCG: garph created successfully")
49+
}
50+
return WeightedCG
51+
}
52+
53+
// RedirectEdges deletes and redirect out-edges from node cur.
54+
func RedirectEdges(g *IrGraph, cur *IrNode) {
55+
for i, outEdge := range g.OutEdges[cur] {
56+
if !InlMap[outEdge.CallSite] {
57+
for _, InEdge := range g.InEdges[cur] {
58+
if InlMap[InEdge.CallSite] {
59+
weight := calculteweight(g, InEdge.Src, cur)
60+
redirectEdge(g, InEdge.Src, cur, outEdge, weight, i)
61+
}
62+
}
63+
} else {
64+
remove(g, cur, i, outEdge.Dst.NodeIr.Nname)
65+
}
66+
}
67+
removeall(g, cur)
68+
}
69+
70+
// calculteweight calculates the weight of the new redircted edges.
71+
func calculteweight(g *IrGraph, parent *IrNode, cur *IrNode) int64 {
72+
sum := int64(0)
73+
pw := int64(0)
74+
for _, InEdge := range g.InEdges[cur] {
75+
sum = sum + InEdge.Weight
76+
if InEdge.Src == parent {
77+
pw = InEdge.Weight
78+
}
79+
}
80+
weight := int64(0)
81+
if sum != 0 {
82+
weight = pw / sum
83+
} else {
84+
weight = pw
85+
}
86+
return weight
87+
}
88+
89+
// redirectEdge deletes the cur-node's out-edges and redirct them so now these edges are the parent node out-edges.
90+
func redirectEdge(g *IrGraph, parent *IrNode, cur *IrNode, outEdge *IrEdge, weight int64, idx int) {
91+
outEdge.Src = parent
92+
outEdge.Weight = weight * outEdge.Weight
93+
g.OutEdges[parent] = append(g.OutEdges[parent], outEdge)
94+
remove(g, cur, idx, outEdge.Dst.NodeIr.Nname)
95+
}
96+
97+
// remove deletes the cur-node's out-edges at index idx.
98+
func remove(g *IrGraph, cur *IrNode, idx int, name *ir.Name) {
99+
if len(g.OutEdges[cur]) >= 2 {
100+
g.OutEdges[cur][idx] = &IrEdge{CallSite: "removed"}
101+
} else {
102+
delete(g.OutEdges, cur)
103+
}
104+
}
105+
106+
// removeall deletes all cur-node's out-edges that marked to be removed .
107+
func removeall(g *IrGraph, cur *IrNode) {
108+
for i := len(g.OutEdges[cur]) - 1; i >= 0; i-- {
109+
if g.OutEdges[cur][i].CallSite == "removed" {
110+
g.OutEdges[cur][i] = g.OutEdges[cur][len(g.OutEdges[cur])-1]
111+
g.OutEdges[cur] = g.OutEdges[cur][:len(g.OutEdges[cur])-1]
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)