Skip to content

Commit

Permalink
cmd/go: use critical path scheduling
Browse files Browse the repository at this point in the history
DO NOT REVIEW

This is a straight port of Dmitry's CL 13235046.

TODO:

* Understand better how it works.
* Assign better weights.
* Convince myself that it is an improvement.
  Early measurements are not yet compelling,
  although that could be because the weights
  are not well-chosen.

Notes on weights from the issue:

link > compile
run test > link
Fake instant actions << everything else
cgo increases weights

Fixes golang#8893.

Change-Id: I8fba875a9a56aed132bd6b37b607e374f336728d
  • Loading branch information
josharian committed Apr 14, 2015
1 parent eced964 commit f7c378a
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 30 deletions.
65 changes: 46 additions & 19 deletions src/cmd/go/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ type action struct {

f func(*builder, *action) error // the action itself (nil = no-op)
ignoreFail bool // whether to run f even if dependencies fail
weight int // estimated execution time (unitless)

// Generated files, directories.
link bool // target is executable, not just package
Expand Down Expand Up @@ -789,31 +790,57 @@ func actionList(root *action) []*action {
return all
}

// do runs the action graph rooted at root.
func (b *builder) do(root *action) {
// Build list of all actions, assigning depth-first post-order priority.
// The original implementation here was a true queue
// (using a channel) but it had the effect of getting
// distracted by low-level leaf actions to the detriment
// of completing higher-level actions. The order of
// work does not matter much to overall execution time,
// but when running "go test std" it is nice to see each test
// results as soon as possible. The priorities assigned
// ensure that, all else being equal, the execution prefers
// to do what it would have done first in a simple depth-first
// dependency order traversal.
all := actionList(root)
for i, a := range all {
a.priority = i
func assignPriorities(all []*action) {
for _, a := range all {
a.weight = 1 // TODO: better weight estimation
a.priority = -1
}

b.readySema = make(chan bool, len(all))
for assigned := 0; assigned < len(all); {
for _, a := range all {
if a.priority != -1 {
continue
}
assigned++
a.priority = 0
for _, a1 := range a.triggers {
if a1.priority == -1 {
assigned--
a.priority = -1
break
}
if a.priority < a1.priority+1 {
a.priority = a1.priority + a.weight
}
}
}
}

// Initialize per-action execution state.
// TODO: DELETE
// aq := actionQueue(all)
// sort.Sort(&aq)
// for _, a := range aq {
// if a.p != nil {
// fmt.Println("*** ", a.p.ImportPath, a.priority)
// } else {
// fmt.Println("--- ", a.objpkg, a.priority)
// }
// }
}

// do runs the action graph rooted at root.
func (b *builder) do(root *action) {
all := actionList(root)
for _, a := range all {
for _, a1 := range a.deps {
a1.triggers = append(a1.triggers, a)
}
}
assignPriorities(all)

// Initialize per-action execution state.
b.readySema = make(chan bool, len(all))
for _, a := range all {
a.pending = len(a.deps)
if a.pending == 0 {
b.ready.push(a)
Expand Down Expand Up @@ -2761,7 +2788,7 @@ type actionQueue []*action
// Implement heap.Interface
func (q *actionQueue) Len() int { return len(*q) }
func (q *actionQueue) Swap(i, j int) { (*q)[i], (*q)[j] = (*q)[j], (*q)[i] }
func (q *actionQueue) Less(i, j int) bool { return (*q)[i].priority < (*q)[j].priority }
func (q *actionQueue) Less(i, j int) bool { return (*q)[i].priority > (*q)[j].priority }
func (q *actionQueue) Push(x interface{}) { *q = append(*q, x.(*action)) }
func (q *actionQueue) Pop() interface{} {
n := len(*q) - 1
Expand Down
19 changes: 8 additions & 11 deletions src/cmd/go/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
computeStale(pmain)

if ptest != p {
// Put the package into action cache with correct parameters.
a := b.action(modeBuild, modeBuild, ptest)
a.objdir = testDir + string(filepath.Separator) + "_obj_test" + string(filepath.Separator)
a.objpkg = ptestObj
Expand All @@ -805,6 +806,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
}

if pxtest != nil {
// Put the package into action cache with correct parameters.
a := b.action(modeBuild, modeBuild, pxtest)
a.objdir = testDir + string(filepath.Separator) + "_obj_xtest" + string(filepath.Separator)
a.objpkg = buildToolchain.pkgpath(testDir, pxtest)
Expand Down Expand Up @@ -874,14 +876,9 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
p: p,
ignoreFail: true,
}
cleanAction := &action{
f: (*builder).cleanTest,
deps: []*action{runAction},
p: p,
}
printAction = &action{
f: (*builder).printTest,
deps: []*action{cleanAction},
deps: []*action{runAction},
p: p,
}
}
Expand Down Expand Up @@ -978,6 +975,8 @@ func declareCoverVars(importPath string, files ...string) map[string]*CoverVar {

// runTest is the action for running a test binary.
func (b *builder) runTest(a *action) error {
defer b.cleanTest(a)

args := stringList(findExecCmd(), a.deps[0].target, testArgs)
a.testOutput = new(bytes.Buffer)

Expand Down Expand Up @@ -1105,20 +1104,18 @@ func coveragePercentage(out []byte) string {
}

// cleanTest is the action for cleaning up after a test.
func (b *builder) cleanTest(a *action) error {
func (b *builder) cleanTest(a *action) {
if buildWork {
return nil
return
}
run := a.deps[0]
testDir := filepath.Join(b.work, filepath.FromSlash(run.p.ImportPath+"/_test"))
os.RemoveAll(testDir)
return nil
}

// printTest is the action for printing a test result.
func (b *builder) printTest(a *action) error {
clean := a.deps[0]
run := clean.deps[0]
run := a.deps[0]
os.Stdout.Write(run.testOutput.Bytes())
run.testOutput = nil
return nil
Expand Down

0 comments on commit f7c378a

Please sign in to comment.