diff --git a/report/latest_map.go b/report/latest_map.go index da767c1c7..3247ca037 100644 --- a/report/latest_map.go +++ b/report/latest_map.go @@ -56,9 +56,9 @@ func (m LatestMap) Size() int { return m.Map.Size() } -// Merge produces a fresh StringLatestMap containing the keys from -// both inputs. When both inputs contain the same key, the newer value -// is used. +// Merge produces a fresh LatestMap, containing the keys from both +// inputs. When both inputs contain the same key, the newer value is +// used. func (m LatestMap) Merge(other LatestMap) LatestMap { var ( mSize = m.Size() @@ -71,7 +71,45 @@ func (m LatestMap) Merge(other LatestMap) LatestMap { return other case otherSize == 0: return m - case mSize < otherSize: + case mSize*2 < otherSize: + // We are selecting between two merge strategies here: 1) + // "merge shorter map into longer map" (this branch and the + // next), and 2) "merge older map into younger map" (last + // branch). + // + // The "*2" results from the assumptions that a) all the keys + // in the smaller map are in the larger map, and b) all the + // entries in one map are younger than the entries in the + // other. This is typically the case in our usage. + // Furthermore, we assume that adding and setting an entry + // carries the same cost. This is the case for ps.Map. + // + // Let len(s) and len(l) be the lengths of the shorter and + // longer maps, respectively. + // + // It is then the case that the worst case complexity of the + // "merge shorter map into longer map" strategy is len(s), + // which happens when all the entries in the shorter map are + // in fact older than their corresponding entries in the + // longer map, which means we need to set all of them. + // + // By contrast, the worst case complexity of the "merge older + // map into younger map" strategy is len(l)-len(s), which + // happens when all the entries in the shorter map are younger + // than their corresponding entries in the longer map, which + // means we need to add all the entries from the longer map + // that aren't in the shorter map. + // + // Therefore the cut-over point between the two merge + // strategies is when len(s) = len(l)-len(s), i.e. len(s)*2 = + // len(l). + output, iter = iter, output + case otherSize*2 < mSize: + // As above, but in reverse. + case output.First().(LatestEntry).Timestamp.Before(iter.First().(LatestEntry).Timestamp): + // See the "all entries in one map are younger than entries in + // the other" assumptions above. We are sampling entries here + // to determine which map contains the younger entries. output, iter = iter, output } if m.decoder != other.decoder { diff --git a/vendor/github.com/weaveworks/ps/map.go b/vendor/github.com/weaveworks/ps/map.go index 98918bff3..7fe693761 100644 --- a/vendor/github.com/weaveworks/ps/map.go +++ b/vendor/github.com/weaveworks/ps/map.go @@ -42,6 +42,9 @@ type Map interface { // This operation is O(log N) in the number of keys. Lookup(key string) (interface{}, bool) + // First returns the "first" value in the map, if any, or nil. + First() interface{} + // Size returns the number of key value pairs in the map. // This takes O(1) time. Size() int @@ -348,6 +351,10 @@ func lookupLowLevel(self *tree, partialHash, hash uint64) (interface{}, bool) { return self.value, true } +func (m *tree) First() interface{} { + return m.value +} + func (m *tree) Size() int { return m.count } diff --git a/vendor/manifest b/vendor/manifest index 423514b6b..461100d16 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -1211,8 +1211,8 @@ "importpath": "github.com/weaveworks/ps", "repository": "https://github.com/weaveworks/ps", "vcs": "git", - "revision": "70d17b2d6f760345ef36a0748751eddf7290a92c", - "branch": "master", + "revision": "26c07ef9a67b5bd2f5707b9a1957e7492776d945", + "branch": "map-first", "notests": true }, {