Parallelize GenerateBuildActions
Modify the Generate phase to call GenerateBuildActions inside a goroutine to allow parallization. All modules from a single moduleGroup are called from a single goroutine, and the goroutines that processed all dependencies must have finished, so the only change to the semantics of GenerateBuildActions implementations is around access to globals. Locks are added to the lazily created package context variables and to the live tracker. Reduces run time by ~33%. Change-Id: I62a515acf86f1dcecb093ded83444b920ff603be
This commit is contained in:
parent
7ad621c2b4
commit
691a60dd98
5 changed files with 136 additions and 23 deletions
|
@ -102,8 +102,15 @@ type moduleGroup struct {
|
|||
|
||||
modules []*moduleInfo
|
||||
|
||||
// set during updateDependencies
|
||||
reverseDeps []*moduleGroup
|
||||
depsCount int
|
||||
|
||||
// set during PrepareBuildActions
|
||||
actionDefs localBuildActions
|
||||
|
||||
// used by parallelVisitAllBottomUp
|
||||
waitingCount int
|
||||
}
|
||||
|
||||
type moduleInfo struct {
|
||||
|
@ -821,7 +828,7 @@ func (c *Context) ResolveDependencies(config interface{}) []error {
|
|||
return errs
|
||||
}
|
||||
|
||||
errs = c.rebuildSortedModuleList()
|
||||
errs = c.updateDependencies()
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
@ -935,12 +942,53 @@ func (c *Context) addDependency(module *moduleInfo, depName string) []error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// rebuildSortedModuleList recursively walks the module dependency graph and
|
||||
// builds a sorted list of modules such that dependencies of a module always
|
||||
// appear first. It also reports errors when it encounters dependency cycles.
|
||||
// This should called after resolveDependencies, as well as after any mutator
|
||||
// pass has called addDependency
|
||||
func (c *Context) rebuildSortedModuleList() (errs []error) {
|
||||
func (c *Context) parallelVisitAllBottomUp(visit func(group *moduleGroup)) {
|
||||
doneCh := make(chan *moduleGroup)
|
||||
count := 0
|
||||
|
||||
for _, group := range c.moduleGroupsSorted {
|
||||
group.waitingCount = group.depsCount
|
||||
}
|
||||
|
||||
visitOne := func(group *moduleGroup) {
|
||||
count++
|
||||
go func() {
|
||||
visit(group)
|
||||
doneCh <- group
|
||||
}()
|
||||
}
|
||||
|
||||
for _, group := range c.moduleGroupsSorted {
|
||||
if group.waitingCount == 0 {
|
||||
visitOne(group)
|
||||
}
|
||||
}
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case doneGroup := <-doneCh:
|
||||
for _, parent := range doneGroup.reverseDeps {
|
||||
parent.waitingCount--
|
||||
if parent.waitingCount == 0 {
|
||||
visitOne(parent)
|
||||
}
|
||||
}
|
||||
count--
|
||||
if count == 0 {
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateDependencies recursively walks the module dependency graph and updates
|
||||
// additional fields based on the dependencies. It builds a sorted list of modules
|
||||
// such that dependencies of a module always appear first, and populates reverse
|
||||
// dependency links and counts of total dependencies. It also reports errors when
|
||||
// it encounters dependency cycles. This should called after resolveDependencies,
|
||||
// as well as after any mutator pass has called addDependency
|
||||
func (c *Context) updateDependencies() (errs []error) {
|
||||
visited := make(map[*moduleGroup]bool) // modules that were already checked
|
||||
checking := make(map[*moduleGroup]bool) // modules actively being checked
|
||||
|
||||
|
@ -960,6 +1008,9 @@ func (c *Context) rebuildSortedModuleList() (errs []error) {
|
|||
}
|
||||
}
|
||||
|
||||
group.reverseDeps = []*moduleGroup{}
|
||||
group.depsCount = len(deps)
|
||||
|
||||
for dep := range deps {
|
||||
if checking[dep] {
|
||||
// This is a cycle.
|
||||
|
@ -1003,6 +1054,8 @@ func (c *Context) rebuildSortedModuleList() (errs []error) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
dep.reverseDeps = append(dep.reverseDeps, group)
|
||||
}
|
||||
|
||||
sorted = append(sorted, group)
|
||||
|
@ -1183,7 +1236,7 @@ func (c *Context) runBottomUpMutator(config interface{},
|
|||
}
|
||||
|
||||
if dependenciesModified {
|
||||
errs = c.rebuildSortedModuleList()
|
||||
errs = c.updateDependencies()
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
@ -1205,7 +1258,26 @@ func (c *Context) generateModuleBuildActions(config interface{},
|
|||
var deps []string
|
||||
var errs []error
|
||||
|
||||
for _, group := range c.moduleGroupsSorted {
|
||||
cancelCh := make(chan struct{})
|
||||
errsCh := make(chan []error)
|
||||
depsCh := make(chan []string)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-cancelCh:
|
||||
close(cancelCh)
|
||||
return
|
||||
case newErrs := <-errsCh:
|
||||
errs = append(errs, newErrs...)
|
||||
case newDeps := <-depsCh:
|
||||
deps = append(deps, newDeps...)
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c.parallelVisitAllBottomUp(func(group *moduleGroup) {
|
||||
// The parent scope of the moduleContext's local scope gets overridden to be that of the
|
||||
// calling Go package on a per-call basis. Since the initial parent scope doesn't matter we
|
||||
// just set it to nil.
|
||||
|
@ -1225,20 +1297,23 @@ func (c *Context) generateModuleBuildActions(config interface{},
|
|||
mctx.module.logicModule.GenerateBuildActions(mctx)
|
||||
|
||||
if len(mctx.errs) > 0 {
|
||||
errs = append(errs, mctx.errs...)
|
||||
errsCh <- mctx.errs
|
||||
break
|
||||
}
|
||||
|
||||
deps = append(deps, mctx.ninjaFileDeps...)
|
||||
depsCh <- mctx.ninjaFileDeps
|
||||
|
||||
newErrs := c.processLocalBuildActions(&group.actionDefs,
|
||||
&mctx.actionDefs, liveGlobals)
|
||||
if len(newErrs) > 0 {
|
||||
errs = append(errs, newErrs...)
|
||||
errsCh <- newErrs
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
cancelCh <- struct{}{}
|
||||
<-cancelCh
|
||||
|
||||
return deps, errs
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ func TestContextParse(t *testing.T) {
|
|||
t.FailNow()
|
||||
}
|
||||
|
||||
errs = ctx.rebuildSortedModuleList()
|
||||
errs = ctx.updateDependencies()
|
||||
if len(errs) > 0 {
|
||||
t.Errorf("unexpected dep cycle errors:")
|
||||
for _, err := range errs {
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package blueprint
|
||||
|
||||
import "sync"
|
||||
|
||||
// A liveTracker tracks the values of live variables, rules, and pools. An
|
||||
// entity is made "live" when it is referenced directly or indirectly by a build
|
||||
// definition. When an entity is made live its value is computed based on the
|
||||
// configuration.
|
||||
type liveTracker struct {
|
||||
sync.Mutex
|
||||
config interface{} // Used to evaluate variable, rule, and pool values.
|
||||
|
||||
variables map[Variable]*ninjaString
|
||||
|
@ -22,6 +25,9 @@ func newLiveTracker(config interface{}) *liveTracker {
|
|||
}
|
||||
|
||||
func (l *liveTracker) AddBuildDefDeps(def *buildDef) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
err := l.addRule(def.Rule)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -61,6 +61,13 @@ import (
|
|||
//
|
||||
// to build the list of library file names that should be included in its link
|
||||
// command.
|
||||
//
|
||||
// GenerateBuildActions may be called from multiple threads. It is guaranteed to
|
||||
// be called after it has finished being called on all dependencies and on all
|
||||
// variants of that appear earlier in the ModuleContext.VisitAllModuleVariants list.
|
||||
// Any accesses to global variables or to Module objects that are not dependencies
|
||||
// or variants of the current Module must be synchronized by the implementation of
|
||||
// GenerateBuildActions.
|
||||
type Module interface {
|
||||
// GenerateBuildActions is called by the Context that created the Module
|
||||
// during its generate phase. This call should generate all Ninja build
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A PackageContext provides a way to create package-scoped Ninja pools,
|
||||
|
@ -543,11 +544,12 @@ func (p *poolFunc) String() string {
|
|||
}
|
||||
|
||||
type staticRule struct {
|
||||
pctx *PackageContext
|
||||
name_ string
|
||||
params RuleParams
|
||||
argNames map[string]bool
|
||||
scope_ *basicScope
|
||||
pctx *PackageContext
|
||||
name_ string
|
||||
params RuleParams
|
||||
argNames map[string]bool
|
||||
scope_ *basicScope
|
||||
sync.Mutex // protects scope_ during lazy creation
|
||||
}
|
||||
|
||||
// StaticRule returns a Rule whose value does not depend on any configuration
|
||||
|
@ -590,7 +592,13 @@ func (p *PackageContext) StaticRule(name string, params RuleParams,
|
|||
|
||||
ruleScope := (*basicScope)(nil) // This will get created lazily
|
||||
|
||||
r := &staticRule{p, name, params, argNamesSet, ruleScope}
|
||||
r := &staticRule{
|
||||
pctx: p,
|
||||
name_: name,
|
||||
params: params,
|
||||
argNames: argNamesSet,
|
||||
scope_: ruleScope,
|
||||
}
|
||||
err = p.scope.AddRule(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -623,6 +631,9 @@ func (r *staticRule) scope() *basicScope {
|
|||
// We lazily create the scope so that all the package-scoped variables get
|
||||
// declared before the args are created. Otherwise we could incorrectly
|
||||
// shadow a package-scoped variable with an arg variable.
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.scope_ == nil {
|
||||
r.scope_ = makeRuleScope(r.pctx.scope, r.argNames)
|
||||
}
|
||||
|
@ -643,6 +654,7 @@ type ruleFunc struct {
|
|||
paramsFunc func(interface{}) (RuleParams, error)
|
||||
argNames map[string]bool
|
||||
scope_ *basicScope
|
||||
sync.Mutex // protects scope_ during lazy creation
|
||||
}
|
||||
|
||||
// RuleFunc returns a Rule whose value is determined by a function that takes a
|
||||
|
@ -686,7 +698,13 @@ func (p *PackageContext) RuleFunc(name string, f func(interface{}) (RuleParams,
|
|||
|
||||
ruleScope := (*basicScope)(nil) // This will get created lazily
|
||||
|
||||
rule := &ruleFunc{p, name, f, argNamesSet, ruleScope}
|
||||
rule := &ruleFunc{
|
||||
pctx: p,
|
||||
name_: name,
|
||||
paramsFunc: f,
|
||||
argNames: argNamesSet,
|
||||
scope_: ruleScope,
|
||||
}
|
||||
err = p.scope.AddRule(rule)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -723,6 +741,9 @@ func (r *ruleFunc) scope() *basicScope {
|
|||
// We lazily create the scope so that all the global variables get declared
|
||||
// before the args are created. Otherwise we could incorrectly shadow a
|
||||
// global variable with an arg variable.
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.scope_ == nil {
|
||||
r.scope_ = makeRuleScope(r.pctx.scope, r.argNames)
|
||||
}
|
||||
|
@ -738,8 +759,9 @@ func (r *ruleFunc) String() string {
|
|||
}
|
||||
|
||||
type builtinRule struct {
|
||||
name_ string
|
||||
scope_ *basicScope
|
||||
name_ string
|
||||
scope_ *basicScope
|
||||
sync.Mutex // protects scope_ during lazy creation
|
||||
}
|
||||
|
||||
func (r *builtinRule) packageContext() *PackageContext {
|
||||
|
@ -759,6 +781,9 @@ func (r *builtinRule) def(config interface{}) (*ruleDef, error) {
|
|||
}
|
||||
|
||||
func (r *builtinRule) scope() *basicScope {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if r.scope_ == nil {
|
||||
r.scope_ = makeRuleScope(nil, nil)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue