]> Cypherpunks repositories - gostls13.git/commitdiff
internal/trace: use banding to optimize MMU computation
authorAustin Clements <austin@google.com>
Tue, 8 Aug 2017 22:15:32 +0000 (18:15 -0400)
committerAustin Clements <austin@google.com>
Mon, 5 Nov 2018 19:10:09 +0000 (19:10 +0000)
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 <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
src/go/build/deps_test.go
src/internal/traceparser/gc.go

index ec6e6b4890c28833d9f7a69f3ebec053df9012bf..d632954d0c4b3f2dd020f703c34a023265f134d2 100644 (file)
@@ -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"},
index 66c68cb4502413efa53a01cf178baae2b97a7da3..0be78e71e3b958a2d56b8303d1535e0090757e59 100644 (file)
@@ -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
                }
        }