optimise LatestMap.Merge by merging old into young

This commit is contained in:
Matthias Radestock
2016-08-23 19:34:05 +01:00
parent e8ebb31075
commit c50155bb7e
3 changed files with 51 additions and 6 deletions

View File

@@ -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 {

View File

@@ -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
}

4
vendor/manifest vendored
View File

@@ -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
},
{