From df48fe5fffa131eb0154d8d598aafa1250d4082c Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 29 Sep 2017 22:02:08 +0000 Subject: [PATCH] Re-implement Sets as sorted slice, for performance --- report/sets.go | 251 ++++++++++++++++++++++++++++++------------------- 1 file changed, 153 insertions(+), 98 deletions(-) diff --git a/report/sets.go b/report/sets.go index feb8f9767..5201ab47b 100644 --- a/report/sets.go +++ b/report/sets.go @@ -1,156 +1,211 @@ package report import ( + "bytes" + "fmt" "reflect" + "sort" "github.com/ugorji/go/codec" - "github.com/weaveworks/ps" ) -// Sets is a string->set-of-strings map. -// It is immutable. -type Sets struct { - psMap ps.Map +// Sets holds StringSet instances, as a slice sorted by key. +type Sets []stringSetEntry + +type stringSetEntry struct { + key string + Value StringSet `json:"value"` } -// EmptySets is an empty Sets. Starts with this. -var emptySets = Sets{ps.NewMap()} - -// MakeSets returns EmptySets +// MakeSets creates an empty Sets func MakeSets() Sets { - return emptySets + return nil } -// Keys returns the keys for this set -func (s Sets) Keys() []string { - if s.psMap == nil { - return nil - } - return s.psMap.Keys() +// locate the position where key should go, either at the end or in the middle +func (m Sets) locate(key string) int { + return sort.Search(len(m), func(i int) bool { + return m[i].key >= key + }) } -// Add the given value to the Sets. -func (s Sets) Add(key string, value StringSet) Sets { - if s.psMap == nil { - s = emptySets - } - if existingValue, ok := s.psMap.Lookup(key); ok { +// Add the given value to the Sets, merging the contents if the key is already there. +func (m Sets) Add(key string, value StringSet) Sets { + i := m.locate(key) + oldM := m + if i == len(m) { // insert at end + m = make([]stringSetEntry, len(oldM)+1) + copy(m, oldM) + } else if m[i].key == key { // merge with existing key var unchanged bool - value, unchanged = existingValue.(StringSet).Merge(value) + value, unchanged = m[i].Value.Merge(value) if unchanged { - return s + return m } + m = make([]stringSetEntry, len(oldM)) + copy(m, oldM) + } else { // insert in the middle + m = make([]stringSetEntry, len(oldM)+1) + copy(m, oldM[:i]) + copy(m[i+1:], oldM[i:]) } - return Sets{ - psMap: s.psMap.Set(key, value), - } + m[i] = stringSetEntry{key: key, Value: value} + return m } // AddString adds a single string under a key, creating a new StringSet if necessary. -func (s Sets) AddString(key string, str string) Sets { - if s.psMap == nil { - s = emptySets - } - value, found := s.Lookup(key) +func (m Sets) AddString(key string, str string) Sets { + value, found := m.Lookup(key) if found && value.Contains(str) { - return s - } - value = value.Add(str) - return Sets{ - psMap: s.psMap.Set(key, value), + return m } + return m.Add(key, StringSet{str}) } // Delete the given set from the Sets. -func (s Sets) Delete(key string) Sets { - if s.psMap == nil { - return emptySets +func (m Sets) Delete(key string) Sets { + i := m.locate(key) + if i == len(m) || m[i].key != key { + return m // not found } - psMap := s.psMap.Delete(key) - if psMap.IsNil() { - return emptySets + result := make([]stringSetEntry, len(m)-1) + if i > 0 { + copy(result, m[:i-1]) } - return Sets{psMap: psMap} + copy(result[i:], m[i+1:]) + return result } -// Lookup returns the sets stored under key. -func (s Sets) Lookup(key string) (StringSet, bool) { - if s.psMap == nil { - return MakeStringSet(), false +// Lookup the value for the given key. +func (m Sets) Lookup(key string) (StringSet, bool) { + i := m.locate(key) + if i < len(m) && m[i].key == key { + return m[i].Value, true } - if value, ok := s.psMap.Lookup(key); ok { - return value.(StringSet), true - } - return MakeStringSet(), false + var zero StringSet + return zero, false } // Size returns the number of elements -func (s Sets) Size() int { - if s.psMap == nil { - return 0 - } - return s.psMap.Size() +func (m Sets) Size() int { + return len(m) } // Merge merges two sets maps into a fresh set, performing set-union merges as // appropriate. -func (s Sets) Merge(other Sets) Sets { - var ( - sSize = s.Size() - otherSize = other.Size() - result = s.psMap - iter = other.psMap - ) +func (m Sets) Merge(n Sets) Sets { switch { - case sSize == 0: - return other - case otherSize == 0: - return s - case sSize < otherSize: - result, iter = iter, result + case len(m) == 0: + return n + case len(n) == 0: + return m + } + if len(n) > len(m) { + m, n = n, m //swap so m is always at least as long as n } - iter.ForEach(func(key string, value interface{}) { - set := value.(StringSet) - if existingSet, ok := result.Lookup(key); ok { - var unchanged bool - set, unchanged = existingSet.(StringSet).Merge(set) - if unchanged { - return - } - } - result = result.Set(key, set) - }) + i, j := 0, 0 + out := make([]stringSetEntry, i, len(m)) - return Sets{result} + for i < len(m) { + switch { + case j >= len(n): + out = append(out, m[i:]...) + return out + case m[i].key == n[j].key: + newValue, _ := m[i].Value.Merge(n[j].Value) + out = append(out, stringSetEntry{key: m[i].key, Value: newValue}) + i++ + j++ + case m[i].key < n[j].key: + out = append(out, m[i]) + i++ + default: + out = append(out, n[j]) + j++ + } + } + out = append(out, n[j:]...) + return out } -func (s Sets) String() string { - return mapToString(s.psMap) +func (m Sets) String() string { + buf := bytes.NewBufferString("{") + for _, val := range m { + fmt.Fprintf(buf, "%s: %v,\n", val.key, val.Value) + } + fmt.Fprintf(buf, "}") + return buf.String() } // DeepEqual tests equality with other Sets -func (s Sets) DeepEqual(t Sets) bool { - return mapEqual(s.psMap, t.psMap, reflect.DeepEqual) +func (m Sets) DeepEqual(n Sets) bool { + if len(m) != len(n) { + return false + } + for i := range m { + if !reflect.DeepEqual(m[i], n[i]) { + return false + } + } + return true } // CodecEncodeSelf implements codec.Selfer -func (s *Sets) CodecEncodeSelf(encoder *codec.Encoder) { - mapWrite(s.psMap, encoder, func(encoder *codec.Encoder, val interface{}) { - encoder.Encode(val.(StringSet)) - }) +// Note this uses undocumented, internal APIs, which could break +// in the future. See https://github.com/weaveworks/scope/pull/1709 +// for more information. +func (m Sets) CodecEncodeSelf(encoder *codec.Encoder) { + z, r := codec.GenHelperEncoder(encoder) + if m == nil { + r.EncodeNil() + return + } + r.EncodeMapStart(len(m)) + for _, val := range m { + z.EncSendContainerState(containerMapKey) + r.EncodeString(cUTF8, val.key) + z.EncSendContainerState(containerMapValue) + val.Value.CodecEncodeSelf(encoder) + } + z.EncSendContainerState(containerMapEnd) } // CodecDecodeSelf implements codec.Selfer -func (s *Sets) CodecDecodeSelf(decoder *codec.Decoder) { - out := mapRead(decoder, func(isNil bool) interface{} { - var value StringSet - if !isNil { - decoder.Decode(&value) +// Uses undocumented, internal APIs as for CodecEncodeSelf. +func (m *Sets) CodecDecodeSelf(decoder *codec.Decoder) { + *m = nil + z, r := codec.GenHelperDecoder(decoder) + if r.TryDecodeAsNil() { + return + } + + length := r.ReadMapStart() + if length > 0 { + *m = make([]stringSetEntry, 0, length) + } + for i := 0; length < 0 || i < length; i++ { + if length < 0 && r.CheckBreak() { + break } - return value - }) - *s = Sets{out} + z.DecSendContainerState(containerMapKey) + var key string + if !r.TryDecodeAsNil() { + key = r.DecodeString() + } + j := m.locate(key) + if j == len(*m) || (*m)[j].key != key { + *m = append(*m, stringSetEntry{}) + copy((*m)[j+1:], (*m)[j:]) + (*m)[j] = stringSetEntry{} + } + (*m)[j].key = key + z.DecSendContainerState(containerMapValue) + if !r.TryDecodeAsNil() { + (*m)[j].Value.CodecDecodeSelf(decoder) + } + } + z.DecSendContainerState(containerMapEnd) } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead