1
0
mirror of https://github.com/golang/go synced 2024-11-23 19:00:04 -07:00

cmd/compile: avoid giant init functions due to many user inits

We generate code that calls each user init function one at a time.
When there are lots of user init functions,
usually due to generated code, like test/rotate* or
github.com/juju/govmomi/vim25/types,
we can end up with a giant function,
which can be slow to compile.

This CL puts in an escape valve.
When there are more than 500 functions, instead of doing:

init.0()
init.1()
// ...

we construct a static array of functions:

var fns = [...]func(){init.0, init.1, ... }

and call them in a loop.

This generates marginally bigger, marginally worse code,
so we restrict it to cases in which it might start to matter.

500 was selected as a mostly arbitrary threshold for "lots".
Each call uses two Progs, one for PCDATA and one for the call,
so at 500 calls we use ~1000 Progs.
At concurrency==8, we get a Prog cache of about
1000 Progs per worker.
So a threshold of 500 should more or less avoid
exhausting the Prog cache in most cases.

Change-Id: I276b887173ddbf65b2164ec9f9b5eb04d8c753c2
Reviewed-on: https://go-review.googlesource.com/41500
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Josh Bleecher Snyder 2017-04-23 17:31:15 -07:00
parent b666f2860b
commit d1b544c7eb
2 changed files with 74 additions and 9 deletions

View File

@ -4,7 +4,9 @@
package gc
import "cmd/compile/internal/types"
import (
"cmd/compile/internal/types"
)
// A function named init is a special case.
// It is called by the initialization before main is run.
@ -114,8 +116,9 @@ func fninit(n []*Node) {
// (6)
for _, s := range types.InitSyms {
if s.Def != nil && s != initsym {
// could check that it is fn of no args/returns
a = nod(OCALL, asNode(s.Def), nil)
n := asNode(s.Def)
n.checkInitFuncSignature()
a = nod(OCALL, n, nil)
r = append(r, a)
}
}
@ -124,11 +127,63 @@ func fninit(n []*Node) {
r = append(r, nf...)
// (8)
// could check that it is fn of no args/returns
for i := 0; i < renameinitgen; i++ {
s := lookupN("init.", i)
a = nod(OCALL, asNode(s.Def), nil)
r = append(r, a)
// maxInlineInitCalls is the threshold at which we switch
// from generating calls inline to generating a static array
// of functions and calling them in a loop.
// See CL 41500 for more discussion.
const maxInlineInitCalls = 500
if renameinitgen < maxInlineInitCalls {
// Not many init functions. Just call them all directly.
for i := 0; i < renameinitgen; i++ {
s := lookupN("init.", i)
n := asNode(s.Def)
n.checkInitFuncSignature()
a = nod(OCALL, n, nil)
r = append(r, a)
}
} else {
// Lots of init functions.
// Set up an array of functions and loop to call them.
// This is faster to compile and similar at runtime.
// Build type [renameinitgen]func().
typ := types.NewArray(functype(nil, nil, nil), int64(renameinitgen))
// Make and fill array.
fnarr := staticname(typ)
fnarr.Name.SetReadonly(true)
for i := 0; i < renameinitgen; i++ {
s := lookupN("init.", i)
lhs := nod(OINDEX, fnarr, nodintconst(int64(i)))
rhs := asNode(s.Def)
rhs.checkInitFuncSignature()
as := nod(OAS, lhs, rhs)
as = typecheck(as, Etop)
genAsStatic(as)
}
// Generate a loop that calls each function in turn.
// for i := 0; i < renameinitgen; i++ {
// fnarr[i]()
// }
i := temp(types.Types[TINT])
fnidx := nod(OINDEX, fnarr, i)
fnidx.SetBounded(true)
zero := nod(OAS, i, nodintconst(0))
cond := nod(OLT, i, nodintconst(int64(renameinitgen)))
incr := nod(OAS, i, nod(OADD, i, nodintconst(1)))
body := nod(OCALL, fnidx, nil)
loop := nod(OFOR, cond, incr)
loop.Nbody.Set1(body)
loop.Ninit.Set1(zero)
loop = typecheck(loop, Etop)
loop = walkstmt(loop)
r = append(r, loop)
}
// (9)
@ -151,3 +206,10 @@ func fninit(n []*Node) {
Curfn = nil
funccompile(fn)
}
func (n *Node) checkInitFuncSignature() {
ft := n.Type.FuncType()
if ft.Receiver.Fields().Len()+ft.Params.Fields().Len()+ft.Results.Fields().Len() > 0 {
Fatalf("init function cannot have receiver, params, or results: %v (%v)", n, n.Type)
}
}

View File

@ -1359,7 +1359,10 @@ func genAsStatic(as *Node) {
Fatalf("genAsStatic: lhs %v", as.Left)
}
if as.Right.Op != OLITERAL {
switch {
case as.Right.Op == OLITERAL:
case as.Right.Op == ONAME && as.Right.Class() == PFUNC:
default:
Fatalf("genAsStatic: rhs %v", as.Right)
}