@@ -29,11 +29,13 @@ package inline
29
29
import (
30
30
"fmt"
31
31
"go/constant"
32
+ "strconv"
32
33
"strings"
33
34
34
35
"cmd/compile/internal/base"
35
36
"cmd/compile/internal/ir"
36
37
"cmd/compile/internal/logopt"
38
+ "cmd/compile/internal/pgo"
37
39
"cmd/compile/internal/typecheck"
38
40
"cmd/compile/internal/types"
39
41
"cmd/internal/obj"
@@ -42,7 +44,8 @@ import (
42
44
43
45
// Inlining budget parameters, gathered in one place
44
46
const (
45
- inlineMaxBudget = 80
47
+ inlineMaxBudget = 80
48
+ // Budget increased due to hotness.
46
49
inlineExtraAppendCost = 0
47
50
// default is to inline if there's at most one call. -l=4 overrides this by using 1 instead.
48
51
inlineExtraCallCost = 57 // 57 was benchmarked to provided most benefit with no bad surprises; see https://github.com/golang/go/issues/19348#issuecomment-439370742
@@ -51,9 +54,87 @@ const (
51
54
52
55
inlineBigFunctionNodes = 5000 // Functions with this many nodes are considered "big".
53
56
inlineBigFunctionMaxCost = 20 // Max cost of inlinee when inlining into a "big" function.
57
+
58
+ )
59
+
60
+ var (
61
+ // Per-caller data structure to track the list of hot call sites. This gets rewritten every caller leaving it to GC for cleanup.
62
+ listOfHotCallSites = make (map [pgo.CallSiteInfo ]struct {})
63
+
64
+ // List of all hot call sites.
65
+ candHotEdgeMap = make (map [string ]struct {})
66
+
67
+ // List of inlined call sites.
68
+ inlinedCallSites = make (map [pgo.CallSiteInfo ]struct {})
69
+
70
+ // Threshold for Hot callsite inlining.
71
+ inlineHotThresholdPercent = float64 (2 )
72
+
73
+ // Budget increased due to hotness.
74
+ inlineHotMaxBudget int32 = 160
54
75
)
55
76
56
- // InlinePackage finds functions that can be inlined and clones them before walk expands them.
77
+ // InlinePrologue records the hot callsites from ir-graph.
78
+ func InlinePrologue () {
79
+ if s , err := strconv .ParseFloat (base .Flag .InlineHotThreshold , 64 ); err == nil {
80
+ inlineHotThresholdPercent = s
81
+ if base .Flag .LowerM != 0 {
82
+ fmt .Printf ("hot-thres=%v\n " , inlineHotThresholdPercent )
83
+ }
84
+ }
85
+
86
+ if base .Flag .InlineHotBudget != 0 {
87
+ inlineHotMaxBudget = int32 (base .Flag .InlineHotBudget )
88
+ }
89
+
90
+ ir .VisitFuncsBottomUp (typecheck .Target .Decls , func (list []* ir.Func , recursive bool ) {
91
+ for _ , f := range list {
92
+ name := ir .PkgFuncName (f )
93
+ if n , ok := pgo .WeightedCG .IRNodes [name ]; ok {
94
+ nodeweight := pgo .WeightInPercentage (n .Flat , pgo .GlobalTotalNodeWeight )
95
+ if nodeweight > inlineHotThresholdPercent {
96
+ n .HotNode = true
97
+ }
98
+ for _ , e := range pgo .WeightedCG .OutEdges [n ] {
99
+ if e .Weight != 0 {
100
+ weightpercent := pgo .WeightInPercentage (e .Weight , pgo .GlobalTotalEdgeWeight )
101
+ if weightpercent > inlineHotThresholdPercent {
102
+ splits := strings .Split (e .CallSite , ":" )
103
+ line2 , _ := strconv .ParseInt (splits [len (splits )- 2 ], 0 , 64 )
104
+ lineno := fmt .Sprintf ("%v" , line2 )
105
+ canonicalName := ir .PkgFuncName (n .AST ) + "-" + lineno + "-" + ir .PkgFuncName (e .Dst .AST )
106
+ if _ , ok := candHotEdgeMap [canonicalName ]; ! ok {
107
+ candHotEdgeMap [canonicalName ] = struct {}{}
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ })
115
+ if base .Flag .LowerM > 4 {
116
+ fmt .Printf ("hot-cg before inline in dot format:" )
117
+ pgo .PrintWeightedCallGraphDOT (inlineHotThresholdPercent )
118
+ }
119
+ }
120
+
121
+ // InlineEpilogue updates IRGraph after inlining.
122
+ func InlineEpilogue () {
123
+ ir .VisitFuncsBottomUp (typecheck .Target .Decls , func (list []* ir.Func , recursive bool ) {
124
+ for _ , f := range list {
125
+ name := ir .PkgFuncName (f )
126
+ if n , ok := pgo .WeightedCG .IRNodes [name ]; ok {
127
+ pgo .RedirectEdges (n , inlinedCallSites )
128
+ }
129
+ }
130
+ })
131
+ if base .Flag .LowerM > 4 {
132
+ fmt .Printf ("hot-cg after inline in dot:" )
133
+ pgo .PrintWeightedCallGraphDOT (inlineHotThresholdPercent )
134
+ }
135
+ }
136
+
137
+ // InlinePackage finds functions that can be inlined and clones them.
57
138
func InlinePackage () {
58
139
ir .VisitFuncsBottomUp (typecheck .Target .Decls , func (list []* ir.Func , recursive bool ) {
59
140
numfns := numNonClosures (list )
@@ -81,6 +162,9 @@ func CanInline(fn *ir.Func) {
81
162
base .Fatalf ("CanInline no nname %+v" , fn )
82
163
}
83
164
165
+ // Initialize an empty list of hot callsites for this caller.
166
+ listOfHotCallSites = make (map [pgo.CallSiteInfo ]struct {})
167
+
84
168
var reason string // reason, if any, that the function was not inlined
85
169
if base .Flag .LowerM > 1 || logopt .Enabled () {
86
170
defer func () {
@@ -181,10 +265,14 @@ func CanInline(fn *ir.Func) {
181
265
budget : inlineMaxBudget ,
182
266
extraCallCost : cc ,
183
267
}
268
+ savefn := ir .CurFunc
269
+ ir .CurFunc = fn
184
270
if visitor .tooHairy (fn ) {
185
271
reason = visitor .reason
272
+ ir .CurFunc = savefn
186
273
return
187
274
}
275
+ ir .CurFunc = savefn
188
276
189
277
n .Func .Inl = & ir.Inline {
190
278
Cost : inlineMaxBudget - visitor .budget ,
@@ -252,6 +340,19 @@ func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
252
340
return true
253
341
}
254
342
if v .budget < 0 {
343
+ if pgo .WeightedCG != nil {
344
+ // Find the existing node in WeightedCallGraph.
345
+ if n , ok := pgo .WeightedCG .IRNodes [ir .PkgFuncName (fn )]; ok {
346
+ // If the cost of hot function is greater than inlineHotMaxBudget,
347
+ // the inliner won't inline this function.
348
+ if inlineMaxBudget - v .budget < inlineHotMaxBudget && n .HotNode == true {
349
+ if base .Flag .LowerM > 1 {
350
+ fmt .Printf ("hot-node enabled increased budget for func=%v\n " , ir .PkgFuncName (fn ))
351
+ }
352
+ return false
353
+ }
354
+ }
355
+ }
255
356
v .reason = fmt .Sprintf ("function too complex: cost %d exceeds budget %d" , inlineMaxBudget - v .budget , inlineMaxBudget )
256
357
return true
257
358
}
@@ -315,6 +416,23 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
315
416
}
316
417
}
317
418
419
+ // Determine if callee edge is a hot callee or not.
420
+ if pgo .WeightedCG != nil && ir .CurFunc != nil {
421
+ if fn := inlCallee (n .X ); fn != nil && typecheck .HaveInlineBody (fn ) {
422
+ lineno := fmt .Sprintf ("%v" , ir .Line (n ))
423
+ splits := strings .Split (lineno , ":" )
424
+ l , _ := strconv .ParseInt (splits [len (splits )- 2 ], 0 , 64 )
425
+ linenum := fmt .Sprintf ("%v" , l )
426
+ canonicalName := ir .PkgFuncName (ir .CurFunc ) + "-" + linenum + "-" + ir .PkgFuncName (fn )
427
+ if _ , o := candHotEdgeMap [canonicalName ]; o {
428
+ if base .Flag .LowerM > 1 {
429
+ fmt .Printf ("hot-callsite identified at line=%v for func=%v\n " , ir .Line (n ), ir .PkgFuncName (ir .CurFunc ))
430
+ }
431
+ listOfHotCallSites [pgo.CallSiteInfo {ir .Line (n ), ir .CurFunc }] = struct {}{}
432
+ }
433
+ }
434
+ }
435
+
318
436
if ir .IsIntrinsicCall (n ) {
319
437
// Treat like any other node.
320
438
break
@@ -464,10 +582,12 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
464
582
465
583
v .budget --
466
584
467
- // When debugging, don't stop early, to get full cost of inlining this function
468
- if v .budget < 0 && base .Flag .LowerM < 2 && ! logopt .Enabled () {
469
- v .reason = "too expensive"
470
- return true
585
+ if pgo .WeightedCG == nil {
586
+ // When debugging, don't stop early, to get full cost of inlining this function
587
+ if v .budget < 0 && base .Flag .LowerM < 2 && ! logopt .Enabled () {
588
+ v .reason = "too expensive"
589
+ return true
590
+ }
471
591
}
472
592
473
593
return ir .DoChildren (n , v .do )
@@ -716,7 +836,18 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlCalls *[]*ir.Inlin
716
836
logopt .LogOpt (n .Pos (), "cannotInlineCall" , "inline" , ir .FuncName (ir .CurFunc ),
717
837
fmt .Sprintf ("cost %d of %s exceeds max large caller cost %d" , fn .Inl .Cost , ir .PkgFuncName (fn ), maxCost ))
718
838
}
719
- return n
839
+
840
+ // If the callsite is hot and it is under the inlineHotMaxBudget budget, then inline it, or else bail.
841
+ if _ , ok := listOfHotCallSites [pgo.CallSiteInfo {ir .Line (n ), ir .CurFunc }]; ok {
842
+ if fn .Inl .Cost > inlineHotMaxBudget {
843
+ return n
844
+ }
845
+ if base .Flag .LowerM > 1 {
846
+ fmt .Printf ("hot-budget check allows inlining at %v\n " , ir .Line (n ))
847
+ }
848
+ } else {
849
+ return n
850
+ }
720
851
}
721
852
722
853
if fn == ir .CurFunc {
@@ -817,7 +948,12 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlCalls *[]*ir.Inlin
817
948
fmt .Printf ("%v: Before inlining: %+v\n " , ir .Line (n ), n )
818
949
}
819
950
951
+ if _ , ok := inlinedCallSites [pgo.CallSiteInfo {ir .Line (n ), ir .CurFunc }]; ! ok {
952
+ inlinedCallSites [pgo.CallSiteInfo {ir .Line (n ), ir .CurFunc }] = struct {}{}
953
+ }
954
+
820
955
res := InlineCall (n , fn , inlIndex )
956
+
821
957
if res == nil {
822
958
base .FatalfAt (n .Pos (), "inlining call to %v failed" , fn )
823
959
}
0 commit comments