From: Austin Clements Date: Tue, 8 Aug 2017 22:15:32 +0000 (-0400) Subject: internal/trace: use banding to optimize MMU computation X-Git-Tag: go1.12beta1~494 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=52ee654b25970a0b8c0ce9d2c8eda604df2026bb;p=gostls13.git internal/trace: use banding to optimize MMU computation This further optimizes MMU construction by first computing a low-resolution summary of the utilization curve. This "band" summary lets us compute the worst-possible window starting in each of these low-resolution bands (even without knowing where in the band the window falls). This in turn lets us compute precise minimum mutator utilization only in the worst low-resolution bands until we can show that any remaining bands can't possibly contain a worse window. This slows down MMU construction for small traces, but these are reasonably fast to compute either way. For large traces (e.g., 150,000+ utilization changes) it's significantly faster. Change-Id: Ie66454e71f3fb06be3f6173b6d91ad75c61bda48 Reviewed-on: https://go-review.googlesource.com/c/60792 Run-TryBot: Austin Clements TryBot-Result: Gobot Gobot Reviewed-by: Hyang-Ah Hana Kim --- diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index ec6e6b4890..d632954d0c 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -273,7 +273,7 @@ var pkgDeps = map[string][]string{ "internal/goroot": {"L4", "OS"}, "internal/singleflight": {"sync"}, "internal/trace": {"L4", "OS"}, - "internal/traceparser": {"L4", "internal/traceparser/filebuf"}, + "internal/traceparser": {"L4", "internal/traceparser/filebuf", "container/heap"}, "internal/traceparser/filebuf": {"L4", "OS"}, "math/big": {"L4"}, "mime": {"L4", "OS", "syscall", "internal/syscall/windows/registry"}, diff --git a/src/internal/traceparser/gc.go b/src/internal/traceparser/gc.go index 66c68cb450..0be78e71e3 100644 --- a/src/internal/traceparser/gc.go +++ b/src/internal/traceparser/gc.go @@ -5,6 +5,8 @@ package traceparser import ( + "container/heap" + "math" "strings" "time" ) @@ -131,6 +133,24 @@ type MMUCurve struct { util []MutatorUtil // sums[j] is the cumulative sum of util[:j]. sums []totalUtil + // bands summarizes util in non-overlapping bands of duration + // bandDur. + bands []mmuBand + // bandDur is the duration of each band. + bandDur int64 +} + +type mmuBand struct { + // minUtil is the minimum instantaneous mutator utilization in + // this band. + minUtil float64 + // cumUtil is the cumulative total mutator utilization between + // time 0 and the left edge of this band. + cumUtil totalUtil + + // integrator is the integrator for the left edge of this + // band. + integrator integrator } // NewMMUCurve returns an MMU curve for the given mutator utilization @@ -146,7 +166,77 @@ func NewMMUCurve(util []MutatorUtil) *MMUCurve { prev = u } - return &MMUCurve{util, sums} + // Divide the utilization curve up into equal size + // non-overlapping "bands" and compute a summary for each of + // these bands. + // + // Compute the duration of each band. + numBands := 1000 + if numBands > len(util) { + // There's no point in having lots of bands if there + // aren't many events. + numBands = len(util) + } + dur := util[len(util)-1].Time - util[0].Time + bandDur := (dur + int64(numBands) - 1) / int64(numBands) + if bandDur < 1 { + bandDur = 1 + } + // Compute the bands. There are numBands+1 bands in order to + // record the final cumulative sum. + bands := make([]mmuBand, numBands+1) + c := MMUCurve{util, sums, bands, bandDur} + leftSum := integrator{&c, 0} + for i := range bands { + startTime, endTime := c.bandTime(i) + cumUtil := leftSum.advance(startTime) + predIdx := leftSum.pos + minUtil := 1.0 + for i := predIdx; i < len(util) && util[i].Time < endTime; i++ { + minUtil = math.Min(minUtil, util[i].Util) + } + bands[i] = mmuBand{minUtil, cumUtil, leftSum} + } + + return &c +} + +func (c *MMUCurve) bandTime(i int) (start, end int64) { + start = int64(i)*c.bandDur + c.util[0].Time + end = start + c.bandDur + return +} + +type bandUtil struct { + // Band index + i int + // Lower bound of mutator utilization for all windows + // with a left edge in this band. + utilBound float64 +} + +type bandUtilHeap []bandUtil + +func (h bandUtilHeap) Len() int { + return len(h) +} + +func (h bandUtilHeap) Less(i, j int) bool { + return h[i].utilBound < h[j].utilBound +} + +func (h bandUtilHeap) Swap(i, j int) { + h[i], h[j] = h[j], h[i] +} + +func (h *bandUtilHeap) Push(x interface{}) { + *h = append(*h, x.(bandUtil)) +} + +func (h *bandUtilHeap) Pop() interface{} { + x := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return x } // MMU returns the minimum mutator utilization for the given time @@ -162,7 +252,88 @@ func (c *MMUCurve) MMU(window time.Duration) (mmu float64) { window = max } + bandU := bandUtilHeap(c.mkBandUtil(window)) + + // Process bands from lowest utilization bound to highest. + heap.Init(&bandU) + + // Refine each band into a precise window and MMU until the + // precise MMU is less than the lowest band bound. mmu = 1.0 + for len(bandU) > 0 && bandU[0].utilBound < mmu { + mmu = c.bandMMU(bandU[0].i, window, mmu) + heap.Pop(&bandU) + } + return mmu +} + +func (c *MMUCurve) mkBandUtil(window time.Duration) []bandUtil { + // For each band, compute the worst-possible total mutator + // utilization for all windows that start in that band. + + // minBands is the minimum number of bands a window can span + // and maxBands is the maximum number of bands a window can + // span in any alignment. + minBands := int((int64(window) + c.bandDur - 1) / c.bandDur) + maxBands := int((int64(window) + 2*(c.bandDur-1)) / c.bandDur) + if window > 1 && maxBands < 2 { + panic("maxBands < 2") + } + tailDur := int64(window) % c.bandDur + nUtil := len(c.bands) - maxBands + 1 + if nUtil < 0 { + nUtil = 0 + } + bandU := make([]bandUtil, nUtil) + for i := range bandU { + // To compute the worst-case MU, we assume the minimum + // for any bands that are only partially overlapped by + // some window and the mean for any bands that are + // completely covered by all windows. + var util totalUtil + + // Find the lowest and second lowest of the partial + // bands. + l := c.bands[i].minUtil + r1 := c.bands[i+minBands-1].minUtil + r2 := c.bands[i+maxBands-1].minUtil + minBand := math.Min(l, math.Min(r1, r2)) + // Assume the worst window maximally overlaps the + // worst minimum and then the rest overlaps the second + // worst minimum. + if minBands == 1 { + util += totalUtilOf(minBand, int64(window)) + } else { + util += totalUtilOf(minBand, c.bandDur) + midBand := 0.0 + switch { + case minBand == l: + midBand = math.Min(r1, r2) + case minBand == r1: + midBand = math.Min(l, r2) + case minBand == r2: + midBand = math.Min(l, r1) + } + util += totalUtilOf(midBand, tailDur) + } + + // Add the total mean MU of bands that are completely + // overlapped by all windows. + if minBands > 2 { + util += c.bands[i+minBands-1].cumUtil - c.bands[i+1].cumUtil + } + + bandU[i] = bandUtil{i, util.mean(window)} + } + + return bandU +} + +// bandMMU computes the precise minimum mutator utilization for +// windows with a left edge in band bandIdx. +func (c *MMUCurve) bandMMU(bandIdx int, window time.Duration, curMMU float64) (mmu float64) { + util := c.util + mmu = curMMU // We think of the mutator utilization over time as the // box-filtered utilization function, which we call the @@ -179,9 +350,12 @@ func (c *MMUCurve) MMU(window time.Duration) (mmu float64) { // We compute the mutator utilization function incrementally // by tracking the integral from t=0 to the left edge of the // window and to the right edge of the window. - left := integrator{c, 0} + left := c.bands[bandIdx].integrator right := left - time := util[0].Time + time, endTime := c.bandTime(bandIdx) + if utilEnd := util[len(util)-1].Time - int64(window); utilEnd < endTime { + endTime = utilEnd + } for { // Advance edges to time and time+window. mu := (right.advance(time+int64(window)) - left.advance(time)).mean(window) @@ -211,7 +385,7 @@ func (c *MMUCurve) MMU(window time.Duration) (mmu float64) { if time < minTime { time = minTime } - if time > util[len(util)-1].Time-int64(window) { + if time >= endTime { break } }