1
0
mirror of https://github.com/golang/go synced 2024-11-18 10:24:42 -07:00

cmd/compile: refactor inlining parameters; inline panic

Inlining was refactored to perform tuning experiments,
with the "knobs" now set to also inline functions/methods
that include panic(), and -l=4 (inline calls) now expressed
as a change to costs, rather than scattered if-thens.

The -l=4 inline-calls penalty is chosen to be the best
found during experiments; it makes some programs much
larger and slower (notably, the compiler itself) and is
believed to be risky for machine-generated code in general,
which is why it is not the default.  It is also not
well-tested with the debugger and DWARF output.

This change includes an explicit go:noinline applied to the
method that is the largest cause of compiler binary growth
and slowdown for midstack inlining; there are others,
ideally whatever heuristic eventually appears will make
this unnecessary.

Change-Id: Idf7056ed2f961472cf49d2fd154ee98bef9421e2
Reviewed-on: https://go-review.googlesource.com/109918
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
This commit is contained in:
David Chase 2018-04-27 12:13:17 -04:00
parent 7d61ad25f8
commit 87a18c6109
2 changed files with 44 additions and 20 deletions

View File

@ -11,7 +11,7 @@
// making 1 the default and -l disable. Additional levels (beyond -l) may be buggy and // making 1 the default and -l disable. Additional levels (beyond -l) may be buggy and
// are not supported. // are not supported.
// 0: disabled // 0: disabled
// 1: 80-nodes leaf functions, oneliners, lazy typechecking (default) // 1: 80-nodes leaf functions, oneliners, panic, lazy typechecking (default)
// 2: (unassigned) // 2: (unassigned)
// 3: (unassigned) // 3: (unassigned)
// 4: allow non-leaf functions // 4: allow non-leaf functions
@ -34,6 +34,15 @@ import (
"strings" "strings"
) )
// Inlining budget parameters, gathered in one place
const (
inlineMaxBudget = 80
inlineExtraAppendCost = 0
inlineExtraCallCost = inlineMaxBudget // default is do not inline, -l=4 enables by using 1 instead.
inlineExtraPanicCost = 1 // do not penalize inlining panics.
inlineExtraThrowCost = inlineMaxBudget // with current (2018-05/1.11) code, inlining runtime.throw does not help.
)
// Get the function's package. For ordinary functions it's on the ->sym, but for imported methods // Get the function's package. For ordinary functions it's on the ->sym, but for imported methods
// the ->sym can be re-used in the local package, so peel it off the receiver's type. // the ->sym can be re-used in the local package, so peel it off the receiver's type.
func fnpkg(fn *Node) *types.Pkg { func fnpkg(fn *Node) *types.Pkg {
@ -155,19 +164,23 @@ func caninl(fn *Node) {
} }
defer n.Func.SetInlinabilityChecked(true) defer n.Func.SetInlinabilityChecked(true)
const maxBudget = 80 cc := int32(inlineExtraCallCost)
visitor := hairyVisitor{budget: maxBudget} if Debug['l'] == 4 {
cc = 1 // this appears to yield better performance than 0.
}
visitor := hairyVisitor{budget: inlineMaxBudget, extraCallCost: cc}
if visitor.visitList(fn.Nbody) { if visitor.visitList(fn.Nbody) {
reason = visitor.reason reason = visitor.reason
return return
} }
if visitor.budget < 0 { if visitor.budget < 0 {
reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", maxBudget-visitor.budget, maxBudget) reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-visitor.budget, inlineMaxBudget)
return return
} }
n.Func.Inl = &Inline{ n.Func.Inl = &Inline{
Cost: maxBudget - visitor.budget, Cost: inlineMaxBudget - visitor.budget,
Dcl: inlcopylist(n.Name.Defn.Func.Dcl), Dcl: inlcopylist(n.Name.Defn.Func.Dcl),
Body: inlcopylist(fn.Nbody.Slice()), Body: inlcopylist(fn.Nbody.Slice()),
} }
@ -229,8 +242,9 @@ func inlFlood(n *Node) {
// hairyVisitor visits a function body to determine its inlining // hairyVisitor visits a function body to determine its inlining
// hairiness and whether or not it can be inlined. // hairiness and whether or not it can be inlined.
type hairyVisitor struct { type hairyVisitor struct {
budget int32 budget int32
reason string reason string
extraCallCost int32
} }
// Look for anything we want to punt on. // Look for anything we want to punt on.
@ -257,12 +271,18 @@ func (v *hairyVisitor) visit(n *Node) bool {
} }
// Functions that call runtime.getcaller{pc,sp} can not be inlined // Functions that call runtime.getcaller{pc,sp} can not be inlined
// because getcaller{pc,sp} expect a pointer to the caller's first argument. // because getcaller{pc,sp} expect a pointer to the caller's first argument.
//
// runtime.throw is a "cheap call" like panic in normal code.
if n.Left.Op == ONAME && n.Left.Class() == PFUNC && isRuntimePkg(n.Left.Sym.Pkg) { if n.Left.Op == ONAME && n.Left.Class() == PFUNC && isRuntimePkg(n.Left.Sym.Pkg) {
fn := n.Left.Sym.Name fn := n.Left.Sym.Name
if fn == "getcallerpc" || fn == "getcallersp" { if fn == "getcallerpc" || fn == "getcallersp" {
v.reason = "call to " + fn v.reason = "call to " + fn
return true return true
} }
if fn == "throw" {
v.budget -= inlineExtraThrowCost
break
}
} }
if fn := n.Left.Func; fn != nil && fn.Inl != nil { if fn := n.Left.Func; fn != nil && fn.Inl != nil {
@ -277,10 +297,9 @@ func (v *hairyVisitor) visit(n *Node) bool {
} }
// TODO(mdempsky): Budget for OCLOSURE calls if we // TODO(mdempsky): Budget for OCLOSURE calls if we
// ever allow that. See #15561 and #23093. // ever allow that. See #15561 and #23093.
if Debug['l'] < 4 {
v.reason = "non-leaf function" // Call cost for non-leaf inlining.
return true v.budget -= v.extraCallCost
}
// Call is okay if inlinable and we have the budget for the body. // Call is okay if inlinable and we have the budget for the body.
case OCALLMETH: case OCALLMETH:
@ -310,17 +329,16 @@ func (v *hairyVisitor) visit(n *Node) bool {
v.budget -= inlfn.Inl.Cost v.budget -= inlfn.Inl.Cost
break break
} }
if Debug['l'] < 4 { // Call cost for non-leaf inlining.
v.reason = "non-leaf method" v.budget -= v.extraCallCost
return true
}
// Things that are too hairy, irrespective of the budget // Things that are too hairy, irrespective of the budget
case OCALL, OCALLINTER, OPANIC: case OCALL, OCALLINTER:
if Debug['l'] < 4 { // Call cost for non-leaf inlining.
v.reason = "non-leaf op " + n.Op.String() v.budget -= v.extraCallCost
return true
} case OPANIC:
v.budget -= v.extraCallCost
case ORECOVER: case ORECOVER:
// recover matches the argument frame pointer to find // recover matches the argument frame pointer to find
@ -343,6 +361,9 @@ func (v *hairyVisitor) visit(n *Node) bool {
v.reason = "unhandled op " + n.Op.String() v.reason = "unhandled op " + n.Op.String()
return true return true
case OAPPEND:
v.budget -= inlineExtraAppendCost
case ODCLCONST, OEMPTY, OFALL, OLABEL: case ODCLCONST, OEMPTY, OFALL, OLABEL:
// These nodes don't produce code; omit from inlining budget. // These nodes don't produce code; omit from inlining budget.
return false return false

View File

@ -200,6 +200,9 @@ func (v *Value) auxString() string {
return "" return ""
} }
// If/when midstack inlining is enabled (-l=4), the compiler gets both larger and slower.
// Not-inlining this method is a help (*Value.reset and *Block.NewValue0 are similar).
//go:noinline
func (v *Value) AddArg(w *Value) { func (v *Value) AddArg(w *Value) {
if v.Args == nil { if v.Args == nil {
v.resetArgs() // use argstorage v.resetArgs() // use argstorage