From 72a0d9dd1d4a2b68e009537f104d4906015bd22e 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 | 209 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 138 insertions(+), 71 deletions(-) diff --git a/report/sets.go b/report/sets.go index 2775dbeaa..d72080c84 100644 --- a/report/sets.go +++ b/report/sets.go @@ -1,133 +1,200 @@ 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 +type stringSetEntry struct { + key string + Value StringSet `json:"value"` } -// EmptySets is an empty Sets. Starts with this. -var emptySets = Sets{ps.NewMap()} +// Sets is a string->set-of-strings map. +type Sets struct { + entries []stringSetEntry +} // MakeSets returns EmptySets func MakeSets() Sets { - return emptySets + return Sets{} } // Keys returns the keys for this set func (s Sets) Keys() []string { - if s.psMap == nil { - return nil + keys := make([]string, s.Size()) + for i, v := range s.entries { + keys[i] = v.key } - return s.psMap.Keys() + return keys } -// Add the given value to the Sets. +// locate the position where key should go, either at the end or in the middle +func (s Sets) locate(key string) int { + return sort.Search(len(s.entries), func(i int) bool { + return s.entries[i].key >= key + }) +} + +// Add the given value to the Sets, merging the contents if the key is already there. func (s Sets) Add(key string, value StringSet) Sets { - if s.psMap == nil { - s = emptySets - } - if existingValue, ok := s.psMap.Lookup(key); ok { - value = value.Merge(existingValue.(StringSet)) - } - return Sets{ - psMap: s.psMap.Set(key, value), + i := s.locate(key) + oldEntries := s.entries + if i == len(s.entries) { + s.entries = make([]stringSetEntry, len(oldEntries)+1) + copy(s.entries, oldEntries) + } else if s.entries[i].key == key { + value = value.Merge(s.entries[i].Value) + s.entries = make([]stringSetEntry, len(oldEntries)) + copy(s.entries, oldEntries) + } else { + s.entries = make([]stringSetEntry, len(oldEntries)+1) + copy(s.entries, oldEntries[:i]) + copy(s.entries[i+1:], oldEntries[i:]) } + s.entries[i] = stringSetEntry{key: key, Value: value} + return s } // Delete the given set from the Sets. func (s Sets) Delete(key string) Sets { - if s.psMap == nil { - return emptySets + i := s.locate(key) + if i == len(s.entries) || s.entries[i].key != key { + return s // not found } - psMap := s.psMap.Delete(key) - if psMap.IsNil() { - return emptySets - } - return Sets{psMap: psMap} + result := make([]stringSetEntry, len(s.entries)-1) + copy(result, s.entries[:i-1]) + copy(result[i:], s.entries[i+1:]) + return Sets{entries: result} } // Lookup returns the sets stored under key. func (s Sets) Lookup(key string) (StringSet, bool) { - if s.psMap == nil { - return MakeStringSet(), false - } - if value, ok := s.psMap.Lookup(key); ok { - return value.(StringSet), true + i := s.locate(key) + if i < len(s.entries) && s.entries[i].key == key { + return s.entries[i].Value, true } return MakeStringSet(), false } // Size returns the number of elements func (s Sets) Size() int { - if s.psMap == nil { - return 0 - } - return s.psMap.Size() + return len(s.entries) } // 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 m.entries == nil: + return n + case n.entries == nil: + return m } + out := make([]stringSetEntry, 0, len(m.entries)+len(n.entries)) - iter.ForEach(func(key string, value interface{}) { - set := value.(StringSet) - if existingSet, ok := result.Lookup(key); ok { - set = set.Merge(existingSet.(StringSet)) + i, j := 0, 0 + for i < len(m.entries) { + switch { + case j >= len(n.entries) || m.entries[i].key < n.entries[j].key: + out = append(out, m.entries[i]) + i++ + case m.entries[i].key == n.entries[j].key: + newValue := m.entries[i].Value.Merge(n.entries[j].Value) + out = append(out, stringSetEntry{key: m.entries[i].key, Value: newValue}) + i++ + j++ + default: + out = append(out, n.entries[j]) + j++ } - result = result.Set(key, set) - }) - - return Sets{result} + } + for ; j < len(n.entries); j++ { + out = append(out, n.entries[j]) + } + return Sets{out} } func (s Sets) String() string { - return mapToString(s.psMap) + buf := bytes.NewBufferString("{") + for _, val := range s.entries { + fmt.Fprintf(buf, "%s: %s,\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) + if s.Size() != t.Size() { + return false + } + for i := range s.entries { + if !reflect.DeepEqual(s.entries[i], t.entries[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.entries == nil { + r.EncodeNil() + return + } + r.EncodeMapStart(m.Size()) + for _, val := range m.entries { + 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.entries = nil + z, r := codec.GenHelperDecoder(decoder) + if r.TryDecodeAsNil() { + return + } + + length := r.ReadMapStart() + if length > 0 { + m.entries = 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() + } + i := m.locate(key) + if i == len(m.entries) || m.entries[i].key != key { + m.entries = append(m.entries, stringSetEntry{}) + copy(m.entries[i+1:], m.entries[i:]) + } + m.entries[i].key = key + z.DecSendContainerState(containerMapValue) + if !r.TryDecodeAsNil() { + m.entries[i].Value.CodecDecodeSelf(decoder) + } + } + z.DecSendContainerState(containerMapEnd) } // MarshalJSON shouldn't be used, use CodecEncodeSelf instead