Skip to content

Commit

Permalink
cmd/go: use critical path scheduling
Browse files Browse the repository at this point in the history
This is a port of part of CL 13235046, by dvyukov.

Use the critical path method to schedule work.

On my laptop (8 logical cores), this CL reduces
the wall time for some but not all large commands:

go build -a std
old=19.0s	new=13.2s	delta=-30.7%	n=50	p=2e-119

go test -short std
old=65.3s	new=54.4s	delta=-16.8%	n=25	p=1e-45

all.bash
old=318.4s	new=317.9s	delta=-0.16%	n=10	p=0.73

This initial implementation assigns equal weight
to all actions. This is a significant oversimplification.
Making actions more granular and assigning more
accurate weights is future work.
However, this should be enough to improve the CPU
utilization of the compile-all builder.

Note that this causes noticeable stalls in output
when building or testing many packages, since
the scheduling usually does not correspond
to the planned output order. This should improve
as scheduling improves.

Updates golang#8893.

Change-Id: I8fba875a9a56aed132bd6b37b607e374f336728d
  • Loading branch information
josharian committed May 8, 2015
1 parent e8fc93e commit 8dc18a9
Showing 1 changed file with 35 additions and 19 deletions.
54 changes: 35 additions & 19 deletions src/cmd/go/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,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 @@ -1060,31 +1061,46 @@ func allArchiveActions(root *action) []*action {
return r
}

// 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(dvyukov,josh): 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.
// 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 @@ -3215,7 +3231,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

0 comments on commit 8dc18a9

Please sign in to comment.