diff --git a/rangetree/skiplist/skiplist.go b/rangetree/skiplist/skiplist.go index 795f4da..7a440f7 100644 --- a/rangetree/skiplist/skiplist.go +++ b/rangetree/skiplist/skiplist.go @@ -38,6 +38,33 @@ import ( "github.com/Workiva/go-datastructures/slice/skip" ) +// keyed is required as in the rangetree code we often want to compare +// two different types of bundles and this allows us to do so without +// checking for each one. +type keyed interface { + key() uint64 +} + +type skipEntry uint64 + +// Compare is required by the skip.Entry interface. +func (se skipEntry) Compare(other skip.Entry) int { + otherSe := other.(skipEntry) + if se == otherSe { + return 0 + } + + if se > otherSe { + return 1 + } + + return -1 +} + +func (se skipEntry) key() uint64 { + return uint64(se) +} + // isLastDimension simply returns dimension == lastDimension-1. // This panics if dimension >= lastDimension. func isLastDimension(dimension, lastDimension uint64) bool { @@ -64,25 +91,55 @@ func needsDeletion(value, index, number int64) bool { // dimensionalBundle is an intermediate holder up to the last // dimension and represents a wrapper around a skiplist. type dimensionalBundle struct { - key uint64 - sl *skip.SkipList + id uint64 + sl *skip.SkipList } -// Key returns the key for this bundle. Fulfills skip.Entry interface. -func (db *dimensionalBundle) Key() uint64 { - return db.key +// Compare returns a value indicating the relative relationship and the +// provided bundle. +func (db *dimensionalBundle) Compare(e skip.Entry) int { + keyed := e.(keyed) + if db.id == keyed.key() { + return 0 + } + + if db.id > keyed.key() { + return 1 + } + + return -1 +} + +// key returns the key for this bundle. +func (db *dimensionalBundle) key() uint64 { + return db.id } // lastBundle represents a bundle living at the last dimension // of the tree. type lastBundle struct { - key uint64 + id uint64 entry rangetree.Entry } -// Key returns the key for this bundle. Fulfills skip.Entry interface. -func (lb *lastBundle) Key() uint64 { - return lb.key +// Compare returns a value indicating the relative relationship and the +// provided bundle. +func (lb *lastBundle) Compare(e skip.Entry) int { + keyed := e.(keyed) + if lb.id == keyed.key() { + return 0 + } + + if lb.id > keyed.key() { + return 1 + } + + return -1 +} + +// Key returns the key for this bundle. +func (lb *lastBundle) key() uint64 { + return lb.id } type skipListRT struct { @@ -106,7 +163,7 @@ func (rt *skipListRT) add(entry rangetree.Entry) rangetree.Entry { for i := uint64(0); i < rt.dimensions; i++ { value = entry.ValueAtDimension(i) - e = sl.Get(uint64(value))[0] + e = sl.Get(skipEntry(value))[0] if isLastDimension(i, rt.dimensions) { if e != nil { // this is an overwrite lb = e.(*lastBundle) @@ -116,14 +173,14 @@ func (rt *skipListRT) add(entry rangetree.Entry) rangetree.Entry { } // need to add new sl entry - lb = &lastBundle{key: uint64(value), entry: entry} + lb = &lastBundle{id: uint64(value), entry: entry} rt.number++ sl.Insert(lb) return nil } if e == nil { // we need the intermediate dimension - db = &dimensionalBundle{key: uint64(value), sl: skip.New(uint64(0))} + db = &dimensionalBundle{id: uint64(value), sl: skip.New(uint64(0))} sl.Insert(db) } else { db = e.(*dimensionalBundle) @@ -156,7 +213,7 @@ func (rt *skipListRT) get(entry rangetree.Entry) rangetree.Entry { ) for i := uint64(0); i < rt.dimensions; i++ { value = uint64(entry.ValueAtDimension(i)) - e = sl.Get(value)[0] + e = sl.Get(skipEntry(value))[0] if e == nil { return nil } @@ -196,7 +253,7 @@ func (rt *skipListRT) deleteRecursive(sl *skip.SkipList, dimension uint64, value := entry.ValueAtDimension(dimension) if isLastDimension(dimension, rt.dimensions) { - entries := sl.Delete(uint64(value)) + entries := sl.Delete(skipEntry(value)) if entries[0] == nil { return nil } @@ -205,7 +262,7 @@ func (rt *skipListRT) deleteRecursive(sl *skip.SkipList, dimension uint64, return entries[0].(*lastBundle).entry } - db, ok := sl.Get(uint64(value))[0].(*dimensionalBundle) + db, ok := sl.Get(skipEntry(value))[0].(*dimensionalBundle) if !ok { // value was not found return nil } @@ -216,7 +273,7 @@ func (rt *skipListRT) deleteRecursive(sl *skip.SkipList, dimension uint64, } if db.sl.Len() == 0 { - sl.Delete(db.key) + sl.Delete(db) } return result @@ -240,9 +297,9 @@ func (rt *skipListRT) apply(sl *skip.SkipList, dimension uint64, var e skip.Entry - for iter := sl.Iter(uint64(lowValue)); iter.Next(); { + for iter := sl.Iter(skipEntry(lowValue)); iter.Next(); { e = iter.Value() - if int64(e.Key()) >= highValue { + if int64(e.(keyed).key()) >= highValue { break } @@ -283,7 +340,7 @@ func (rt *skipListRT) Query(interval rangetree.Interval) rangetree.Entries { func (rt *skipListRT) flatten(sl *skip.SkipList, dimension uint64, entries *rangetree.Entries) { lastDimension := isLastDimension(dimension, rt.dimensions) - for iter := sl.Iter(0); iter.Next(); { + for iter := sl.Iter(skipEntry(0)); iter.Next(); { if lastDimension { *entries = append(*entries, iter.Value().(*lastBundle).entry) } else { @@ -300,9 +357,9 @@ func (rt *skipListRT) insert(sl *skip.SkipList, dimension, insertDimension uint6 affectedDimension := dimension == insertDimension var iter skip.Iterator if dimension == insertDimension { - iter = sl.Iter(uint64(index)) + iter = sl.Iter(skipEntry(index)) } else { - iter = sl.Iter(0) + iter = sl.Iter(skipEntry(0)) } var toDelete skip.Entries @@ -318,32 +375,30 @@ func (rt *skipListRT) insert(sl *skip.SkipList, dimension, insertDimension uint6 ) continue } - if needsDeletion(int64(e.Key()), index, number) { + if needsDeletion(int64(e.(keyed).key()), index, number) { toDelete = append(toDelete, e) continue } if lastDimension { - e.(*lastBundle).key += uint64(number) + e.(*lastBundle).id += uint64(number) *affected = append(*affected, e.(*lastBundle).entry) } else { - e.(*dimensionalBundle).key += uint64(number) + e.(*dimensionalBundle).id += uint64(number) rt.flatten(e.(*dimensionalBundle).sl, dimension+1, affected) } } if len(toDelete) > 0 { - keys := make([]uint64, 0, len(toDelete)) for _, e := range toDelete { if lastDimension { *deleted = append(*deleted, e.(*lastBundle).entry) } else { rt.flatten(e.(*dimensionalBundle).sl, dimension+1, deleted) } - keys = append(keys, e.Key()) } - sl.Delete(keys...) + sl.Delete(toDelete...) } } diff --git a/slice/skip/entries.go b/slice/skip/entries.go index e733ea6..e6a5da7 100644 --- a/slice/skip/entries.go +++ b/slice/skip/entries.go @@ -22,21 +22,21 @@ import "sort" // where that key would be inserted in this list. This could // be equal to the length of the list which means no suitable entry // point was found. -func (entries Entries) search(key uint64) int { +func (entries Entries) search(e Entry) int { return sort.Search(len(entries), func(i int) bool { - return entries[i].Key() >= key + return entries[i].Compare(e) > -1 }) } // insert will insert the provided entry into this list. func (entries *Entries) insert(entry Entry) Entry { - i := entries.search(entry.Key()) + i := entries.search(entry) if i >= len(*entries) { *entries = append(*entries, entry) return nil } - if (*entries)[i].Key() == entry.Key() { + if (*entries)[i].Compare(entry) == 0 { oldEntry := (*entries)[i] (*entries)[i] = entry return oldEntry @@ -49,13 +49,13 @@ func (entries *Entries) insert(entry Entry) Entry { } // delete will delete the provided key from this list. -func (entries *Entries) delete(key uint64) Entry { - i := entries.search(key) +func (entries *Entries) delete(e Entry) Entry { + i := entries.search(e) if i >= len(*entries) { return nil } - if (*entries)[i].Key() != key { + if (*entries)[i].Compare(e) != 0 { return nil } @@ -68,13 +68,13 @@ func (entries *Entries) delete(key uint64) Entry { // get will return the entry associated with the provided key. // If no such Entry exists, this returns nil. -func (entries Entries) get(key uint64) Entry { - i := entries.search(key) +func (entries Entries) get(e Entry) Entry { + i := entries.search(e) if i >= len(entries) { return nil } - if entries[i].Key() == key { + if entries[i].Compare(e) == 0 { return entries[i] } diff --git a/slice/skip/entries_test.go b/slice/skip/entries_test.go index f33f3ba..aba2346 100644 --- a/slice/skip/entries_test.go +++ b/slice/skip/entries_test.go @@ -52,11 +52,11 @@ func TestEntriesDelete(t *testing.T) { entries := Entries{e1, e2} - result := entries.delete(10) + result := entries.delete(e2) assert.Equal(t, Entries{e1}, entries) assert.Equal(t, e2, result) - result = entries.delete(5) + result = entries.delete(e1) assert.Equal(t, Entries{}, entries) assert.Equal(t, e1, result) } diff --git a/slice/skip/interface.go b/slice/skip/interface.go index deddedd..8cf06f7 100644 --- a/slice/skip/interface.go +++ b/slice/skip/interface.go @@ -19,8 +19,10 @@ package skip // Entry defines items that can be inserted into the skip list. // This will also be the type returned from a query. type Entry interface { - // Key defines this entry's place in the skip list. - Key() uint64 + // Compare this entry to the provided entry. Return a positive + // number if this entry is greater than, 0 if equal, negative + // number if less than. + Compare(Entry) int } // Entries is a typed list of interface Entry. diff --git a/slice/skip/iterator.go b/slice/skip/iterator.go index a8e7a39..e04fb07 100644 --- a/slice/skip/iterator.go +++ b/slice/skip/iterator.go @@ -68,61 +68,3 @@ func (iter *iterator) exhaust() Entries { func nilIterator() *iterator { return &iterator{} } - -// starIterator is used as an iterator by the SkipList*. -type starIterator struct { - entries Entries - iter Iterator - index int -} - -// isExhausted returns a bool indicating if all values have been -// iterated. -func (si *starIterator) isExhausted() bool { - return si.index == iteratorExhausted -} - -// Next returns a bool indicating if there are any further values -// in this iterator. -func (si *starIterator) Next() bool { - if si.isExhausted() { - return false - } - - si.index++ - if si.index >= len(si.entries) { - canNext := si.iter.Next() - if !canNext { - si.index = iteratorExhausted - return false - } - - si.entries = si.iter.Value().(*entryBundle).entries - si.index = 0 - } - - return true -} - -// Value returns an Entry representing the iterator's present -// position in the query. Returns nil if no values remain to iterate. -func (si *starIterator) Value() Entry { - if si.isExhausted() { - return nil - } - - if si.entries == nil || si.index < 0 || si.index >= len(si.entries) { - return nil - } - - return si.entries[si.index] -} - -func (si *starIterator) exhaust() Entries { - entries := make(Entries, 0, 20) - for i := si; i.Next(); { - entries = append(entries, i.Value()) - } - - return entries -} diff --git a/slice/skip/iterator_test.go b/slice/skip/iterator_test.go index 26090e3..e1fede6 100644 --- a/slice/skip/iterator_test.go +++ b/slice/skip/iterator_test.go @@ -55,31 +55,3 @@ func TestIterate(t *testing.T) { assert.False(t, iter.Next()) assert.Nil(t, iter.Value()) } - -func TestStarIterate(t *testing.T) { - e1 := newMockEntry(1) - e2 := newMockEntry(3) - e3 := newMockEntry(4) - - i1 := &starIterator{ - entries: Entries{e1}, - index: -1, - } - - i2 := new(mockIterator) - i2.On(`Next`).Return(true).Once() - i2.On(`Next`).Return(false).Once() - i2.On(`Value`).Return(&entryBundle{entries: Entries{e2, e3}}).Once() - i2.On(`Value`).Return(nil).Once() - - i1.iter = i2 - - assert.True(t, i1.Next()) - assert.Equal(t, e1, i1.Value()) - assert.True(t, i1.Next()) - assert.Equal(t, e2, i1.Value()) - assert.True(t, i1.Next()) - assert.Equal(t, e3, i1.Value()) - assert.False(t, i1.Next()) - assert.Nil(t, i1.Value()) -} diff --git a/slice/skip/mock_test.go b/slice/skip/mock_test.go index 538b873..ec9fd6c 100644 --- a/slice/skip/mock_test.go +++ b/slice/skip/mock_test.go @@ -18,16 +18,23 @@ package skip import "github.com/stretchr/testify/mock" -type mockEntry struct { - key uint64 -} +type mockEntry uint64 + +func (me mockEntry) Compare(other Entry) int { + otherU := other.(mockEntry) + if me == otherU { + return 0 + } + + if me > otherU { + return 1 + } -func (me *mockEntry) Key() uint64 { - return me.key + return -1 } -func newMockEntry(key uint64) *mockEntry { - return &mockEntry{key} +func newMockEntry(key uint64) mockEntry { + return mockEntry(key) } type mockIterator struct { diff --git a/slice/skip/node.go b/slice/skip/node.go index 2fed9e9..3b515f7 100644 --- a/slice/skip/node.go +++ b/slice/skip/node.go @@ -18,25 +18,8 @@ package skip type widths []uint64 -func (widths widths) reset() widths { - for i := range widths { - widths[i] = 0 - } - - return widths -} - type nodes []*node -// reset will mark every index in this list of nodes as nil. -func (ns nodes) reset() nodes { - for i := range ns { - ns[i] = nil - } - - return ns -} - type node struct { // forward denotes the forward pointing pointers in this // node. @@ -49,10 +32,8 @@ type node struct { entry Entry } -// key is a helper method to return the key of the entry associated -// with this node. -func (n *node) key() uint64 { - return n.entry.Key() +func (n *node) Compare(e Entry) int { + return n.entry.Compare(e) } // newNode will allocate and return a new node with the entry diff --git a/slice/skip/skip.go b/slice/skip/skip.go index c389ba7..0a45ee2 100644 --- a/slice/skip/skip.go +++ b/slice/skip/skip.go @@ -39,6 +39,21 @@ SearchByPosition: O(log n) InsertByPosition: O(log n) More information here: http://cglab.ca/~morin/teaching/5408/refs/p90b.pdf + +Benchmarks: +BenchmarkInsert-8 2000000 930 ns/op +BenchmarkGet-8 2000000 989 ns/op +BenchmarkDelete-8 3000000 600 ns/op +BenchmarkPrepend-8 1000000 1468 ns/op +BenchmarkByPosition-8 10000000 202 ns/op +BenchmarkInsertAtPosition-8 3000000 485 ns/op + +CPU profiling has shown that the most expensive thing we do here +is call Compare. A potential optimization for gets only is to +do a binary search in the forward/width lists instead of visiting +every value. We could also use generics if Golang had them and +let the consumer specify primitive types, which would speed up +these operation dramatically. */ package skip @@ -74,7 +89,7 @@ func generateLevel(maxLevel uint8) uint8 { } func insertNode(sl *SkipList, n *node, entry Entry, pos uint64, cache nodes, posCache widths, allowDuplicate bool) Entry { - if !allowDuplicate && n != nil && n.key() == entry.Key() { // a simple update in this case + if !allowDuplicate && n != nil && n.Compare(entry) == 0 { // a simple update in this case oldEntry := n.entry n.entry = entry return oldEntry @@ -174,7 +189,7 @@ func (sl *SkipList) init(ifc interface{}) { sl.head = newNode(nil, sl.maxLevel) } -func (sl *SkipList) search(key uint64, update nodes, widths widths) (*node, uint64) { +func (sl *SkipList) search(e Entry, update nodes, widths widths) (*node, uint64) { if sl.num == 0 { // nothing in the list return nil, 1 } @@ -184,7 +199,7 @@ func (sl *SkipList) search(key uint64, update nodes, widths widths) (*node, uint n := sl.head for i := uint8(0); i <= sl.level; i++ { offset = sl.level - i - for n.forward[offset] != nil && n.forward[offset].entry.Key() < key { + for n.forward[offset] != nil && n.forward[offset].Compare(e) < 0 { pos += n.widths[offset] n = n.forward[offset] } @@ -235,27 +250,27 @@ func (sl *SkipList) searchByPosition(position uint64, update nodes, widths width // Get will retrieve values associated with the keys provided. If an // associated value could not be found, a nil is returned in its place. // This is an O(log n) operation. -func (sl *SkipList) Get(keys ...uint64) Entries { - entries := make(Entries, 0, len(keys)) +func (sl *SkipList) Get(entries ...Entry) Entries { + result := make(Entries, 0, len(entries)) var n *node - for _, key := range keys { - n, _ = sl.search(key, nil, nil) - if n != nil && n.entry.Key() == key { - entries = append(entries, n.entry) + for _, e := range entries { + n, _ = sl.search(e, nil, nil) + if n != nil && n.Compare(e) == 0 { + result = append(result, n.entry) } else { - entries = append(entries, nil) + result = append(result, nil) } } - return entries + return result } // GetWithPosition will retrieve the value with the provided key and // return the position of that value within the list. Returns nil, 0 // if an associated value could not be found. -func (sl *SkipList) GetWithPosition(key uint64) (Entry, uint64) { - n, pos := sl.search(key, nil, nil) +func (sl *SkipList) GetWithPosition(e Entry) (Entry, uint64) { + n, pos := sl.search(e, nil, nil) if n == nil { return nil, 0 } @@ -274,9 +289,7 @@ func (sl *SkipList) ByPosition(position uint64) Entry { } func (sl *SkipList) insert(entry Entry) Entry { - sl.cache.reset() - sl.posCache.reset() - n, pos := sl.search(entry.Key(), sl.cache, sl.posCache) + n, pos := sl.search(entry, sl.cache, sl.posCache) return insertNode(sl, n, entry, pos, sl.cache, sl.posCache, false) } @@ -296,8 +309,6 @@ func (sl *SkipList) insertAtPosition(position uint64, entry Entry) { if position > sl.num { position = sl.num } - sl.cache.reset() - sl.posCache.reset() n, pos := sl.searchByPosition(position, sl.cache, sl.posCache) insertNode(sl, n, entry, pos, sl.cache, sl.posCache, true) } @@ -326,12 +337,10 @@ func (sl *SkipList) ReplaceAtPosition(position uint64, entry Entry) { sl.replaceAtPosition(position, entry) } -func (sl *SkipList) delete(key uint64) Entry { - sl.cache.reset() - sl.posCache.reset() - n, _ := sl.search(key, sl.cache, sl.posCache) +func (sl *SkipList) delete(e Entry) Entry { + n, _ := sl.search(e, sl.cache, sl.posCache) - if n == nil || n.entry.Key() != key { + if n == nil || n.Compare(e) != 0 { return nil } @@ -360,11 +369,11 @@ func (sl *SkipList) delete(key uint64) Entry { // Delete will remove the provided keys from the skiplist and return // a list of in-order entries that were deleted. This is a no-op if // an associated key could not be found. This is an O(log n) operation. -func (sl *SkipList) Delete(keys ...uint64) Entries { - deleted := make(Entries, 0, len(keys)) +func (sl *SkipList) Delete(entries ...Entry) Entries { + deleted := make(Entries, 0, len(entries)) - for _, key := range keys { - deleted = append(deleted, sl.delete(key)) + for _, e := range entries { + deleted = append(deleted, sl.delete(e)) } return deleted @@ -375,8 +384,8 @@ func (sl *SkipList) Len() uint64 { return sl.num } -func (sl *SkipList) iter(key uint64) *iterator { - n, _ := sl.search(key, nil, nil) +func (sl *SkipList) iter(e Entry) *iterator { + n, _ := sl.search(e, nil, nil) if n == nil { return nilIterator() } @@ -390,8 +399,8 @@ func (sl *SkipList) iter(key uint64) *iterator { // Iter will return an iterator that can be used to iterate // over all the values with a key equal to or greater than // the key provided. -func (sl *SkipList) Iter(key uint64) Iterator { - return sl.iter(key) +func (sl *SkipList) Iter(e Entry) Iterator { + return sl.iter(e) } func (sl *SkipList) SplitAt(index uint64) (*SkipList, *SkipList) { diff --git a/slice/skip/skip_test.go b/slice/skip/skip_test.go index 22a046a..1cd4b68 100644 --- a/slice/skip/skip_test.go +++ b/slice/skip/skip_test.go @@ -77,8 +77,8 @@ func TestSplitAt(t *testing.T) { left, right := sl.SplitAt(1) assert.Equal(t, uint64(2), left.Len()) assert.Equal(t, uint64(1), right.Len()) - assert.Equal(t, Entries{m1, m2, nil}, left.Get(m1.Key(), m2.Key(), m3.Key())) - assert.Equal(t, Entries{nil, nil, m3}, right.Get(m1.Key(), m2.Key(), m3.Key())) + assert.Equal(t, Entries{m1, m2, nil}, left.Get(m1, m2, m3)) + assert.Equal(t, Entries{nil, nil, m3}, right.Get(m1, m2, m3)) assert.Equal(t, m1, left.ByPosition(0)) assert.Equal(t, m2, left.ByPosition(1)) assert.Equal(t, m3, right.ByPosition(0)) @@ -96,14 +96,14 @@ func TestSplitLargeSkipList(t *testing.T) { left, right := sl.SplitAt(49) assert.Equal(t, uint64(50), left.Len()) for _, le := range leftEntries { - result, index := left.GetWithPosition(le.Key()) + result, index := left.GetWithPosition(le) assert.Equal(t, le, result) assert.Equal(t, le, left.ByPosition(index)) } assert.Equal(t, uint64(50), right.Len()) for _, re := range rightEntries { - result, index := right.GetWithPosition(re.Key()) + result, index := right.GetWithPosition(re) assert.Equal(t, re, result) assert.Equal(t, re, right.ByPosition(index)) } @@ -119,14 +119,14 @@ func TestSplitLargeSkipListOddNumber(t *testing.T) { left, right := sl.SplitAt(49) assert.Equal(t, uint64(50), left.Len()) for _, le := range leftEntries { - result, index := left.GetWithPosition(le.Key()) + result, index := left.GetWithPosition(le) assert.Equal(t, le, result) assert.Equal(t, le, left.ByPosition(index)) } assert.Equal(t, uint64(49), right.Len()) for _, re := range rightEntries { - result, index := right.GetWithPosition(re.Key()) + result, index := right.GetWithPosition(re) assert.Equal(t, re, result) assert.Equal(t, re, right.ByPosition(index)) } @@ -148,11 +148,11 @@ func TestGetWithPosition(t *testing.T) { sl := New(uint8(0)) sl.Insert(m1, m2) - e, pos := sl.GetWithPosition(m1.Key()) + e, pos := sl.GetWithPosition(m1) assert.Equal(t, m1, e) assert.Equal(t, uint64(0), pos) - e, pos = sl.GetWithPosition(m2.Key()) + e, pos = sl.GetWithPosition(m2) assert.Equal(t, m2, e) assert.Equal(t, uint64(1), pos) } @@ -175,7 +175,7 @@ func TestInsertRandomGetByPosition(t *testing.T) { sl.Insert(entries...) for _, e := range entries { - _, pos := sl.GetWithPosition(e.Key()) + _, pos := sl.GetWithPosition(e) assert.Equal(t, e, sl.ByPosition(pos)) } } @@ -196,11 +196,11 @@ func TestGetPositionAfterDelete(t *testing.T) { sl := New(uint8(0)) sl.Insert(m1, m2) - sl.Delete(5) + sl.Delete(m1) assert.Equal(t, m2, sl.ByPosition(0)) assert.Nil(t, sl.ByPosition(1)) - sl.Delete(6) + sl.Delete(m2) assert.Nil(t, sl.ByPosition(0)) assert.Nil(t, sl.ByPosition(1)) } @@ -214,7 +214,7 @@ func TestGetPositionBulkDelete(t *testing.T) { sl.Insert(e2...) for _, e := range e1 { - sl.Delete(e.Key()) + sl.Delete(e) } for i, e := range e2 { assert.Equal(t, e, sl.ByPosition(uint64(i))) @@ -228,14 +228,14 @@ func TestSimpleInsert(t *testing.T) { sl := New(uint8(0)) overwritten := sl.Insert(m1) - assert.Equal(t, Entries{m1}, sl.Get(5)) + assert.Equal(t, Entries{m1}, sl.Get(m1)) assert.Equal(t, uint64(1), sl.Len()) assert.Equal(t, Entries{nil}, overwritten) - assert.Equal(t, Entries{nil}, sl.Get(1)) + assert.Equal(t, Entries{nil}, sl.Get(mockEntry(1))) overwritten = sl.Insert(m2) - assert.Equal(t, Entries{m2}, sl.Get(6)) - assert.Equal(t, Entries{nil}, sl.Get(7)) + assert.Equal(t, Entries{m2}, sl.Get(m2)) + assert.Equal(t, Entries{nil}, sl.Get(mockEntry(7))) assert.Equal(t, uint64(2), sl.Len()) assert.Equal(t, Entries{nil}, overwritten) } @@ -264,7 +264,7 @@ func TestInsertOutOfOrder(t *testing.T) { overwritten := sl.Insert(m1, m2) assert.Equal(t, Entries{nil, nil}, overwritten) - assert.Equal(t, Entries{m1, m2}, sl.Get(6, 5)) + assert.Equal(t, Entries{m1, m2}, sl.Get(m1, m2)) } func TestSimpleDelete(t *testing.T) { @@ -272,12 +272,12 @@ func TestSimpleDelete(t *testing.T) { sl := New(uint8(0)) sl.Insert(m1) - deleted := sl.Delete(m1.Key()) + deleted := sl.Delete(m1) assert.Equal(t, Entries{m1}, deleted) assert.Equal(t, uint64(0), sl.Len()) - assert.Equal(t, Entries{nil}, sl.Get(5)) + assert.Equal(t, Entries{nil}, sl.Get(m1)) - deleted = sl.Delete(5) + deleted = sl.Delete(m1) assert.Equal(t, Entries{nil}, deleted) } @@ -287,10 +287,10 @@ func TestDeleteAll(t *testing.T) { sl := New(uint8(0)) sl.Insert(m1, m2) - deleted := sl.Delete(m1.Key(), m2.Key()) + deleted := sl.Delete(m1, m2) assert.Equal(t, Entries{m1, m2}, deleted) assert.Equal(t, uint64(0), sl.Len()) - assert.Equal(t, Entries{nil, nil}, sl.Get(m1.Key(), m2.Key())) + assert.Equal(t, Entries{nil, nil}, sl.Get(m1, m2)) } func TestIter(t *testing.T) { @@ -300,19 +300,19 @@ func TestIter(t *testing.T) { sl.Insert(m1, m2) - iter := sl.Iter(0) + iter := sl.Iter(mockEntry(0)) assert.Equal(t, Entries{m1, m2}, iter.exhaust()) - iter = sl.Iter(5) + iter = sl.Iter(mockEntry(5)) assert.Equal(t, Entries{m1, m2}, iter.exhaust()) - iter = sl.Iter(6) + iter = sl.Iter(mockEntry(6)) assert.Equal(t, Entries{m2}, iter.exhaust()) - iter = sl.Iter(10) + iter = sl.Iter(mockEntry(10)) assert.Equal(t, Entries{m2}, iter.exhaust()) - iter = sl.Iter(11) + iter = sl.Iter(mockEntry(11)) assert.Equal(t, Entries{}, iter.exhaust()) } @@ -339,7 +339,7 @@ func BenchmarkGet(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - sl.Get(entries[i%numItems].Key()) + sl.Get(entries[i%numItems]) } } @@ -353,7 +353,7 @@ func BenchmarkDelete(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - sl.Delete(entries[i].Key()) + sl.Delete(entries[i]) } } @@ -387,3 +387,15 @@ func BenchmarkByPosition(b *testing.B) { sl.ByPosition(uint64(i % numItems)) } } + +func BenchmarkInsertAtPosition(b *testing.B) { + numItems := b.N + sl := New(uint64(0)) + entries := generateRandomMockEntries(numItems) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + sl.InsertAtPosition(0, entries[i%numItems]) + } +} diff --git a/slice/skip/skipstar.go b/slice/skip/skipstar.go deleted file mode 100644 index e5846e0..0000000 --- a/slice/skip/skipstar.go +++ /dev/null @@ -1,198 +0,0 @@ -/* -Copyright 2014 Workiva, LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* -SkipList* is a data structure combining elements of both a skiplist -and the bottom half of a Y-fast trie. The idea is that you can quickly -search for a sub branch of the skip list and that this branch can -fit entirely within cache, thereby improving the performance characteristics -over a standard skip list. This also keeps any memcopy operation limited -to O(log M) where M is the size of the desired universe. - -Performance vs standard skip list. -BenchmarkInsert-8 2000000 976 ns/op -BenchmarkGet-8 3000000 442 ns/op -BenchmarkDelete-8 3000000 426 ns/op -BenchmarkPrepend-8 2000000 932 ns/op -BenchmarkStarInsert-8 3000000 398 ns/op -BenchmarkStarGet-8 5000000 488 ns/op -BenchmarkStarDelete-8 3000000 440 ns/op -BenchmarkIterStar-8 30000 122984 ns/op -BenchmarkStarPrepend-8 3000000 618 ns/op - -*/ -package skip - -// SkipList* implements all methods of a standard skip list but attempts -// to improve performance by ensuring cache locality. -type SkipListStar struct { - ary uint8 - num uint64 - sl *SkipList -} - -// entryBundle is a helper struct used to define the nodes that -// can be inserted into the SkipList*. -type entryBundle struct { - key uint64 - entries Entries -} - -// Key will return the key associated with this entity bundle. -// This is required by the Entry interface. -func (eb *entryBundle) Key() uint64 { - return eb.key -} - -func newEntryBundle(key uint64, size uint8) *entryBundle { - return &entryBundle{ - key: key, - entries: make(Entries, 0, size), - } -} - -func (ssl *SkipListStar) init(ifc interface{}) { - switch ifc.(type) { - case uint8: - ssl.ary = 8 - case uint16: - ssl.ary = 16 - case uint32: - ssl.ary = 32 - case uint64, uint: - ssl.ary = 64 - } - ssl.sl = New(ifc) -} - -func (ssl *SkipListStar) getNormalizedKey(key uint64) uint64 { - key = key/uint64(ssl.ary) + 1 - return key * uint64(ssl.ary) -} - -func (ssl *SkipListStar) insert(entry Entry) Entry { - key := ssl.getNormalizedKey(entry.Key()) - eb, ok := ssl.sl.Get(key)[0].(*entryBundle) - if !ok { - eb = newEntryBundle(key, ssl.ary) - ssl.sl.Insert(eb) - } - - e := eb.entries.insert(entry) - if e == nil { - ssl.num++ - } - return e -} - -// Insert will insert the provded entries into the SkipList*. Any -// existing entry with a matching key will be overwritten. The returned -// list of a list of entries that were overwritten, in order. A nil -// will be in the in-order position for any non-overwritten entries. -func (ssl *SkipListStar) Insert(entries ...Entry) Entries { - overwritten := make(Entries, 0, len(entries)) - for _, e := range entries { - overwritten = append(overwritten, ssl.insert(e)) - } - - return overwritten -} - -func (ssl *SkipListStar) get(key uint64) Entry { - normalizedKey := ssl.getNormalizedKey(key) - eb, ok := ssl.sl.Get(normalizedKey)[0].(*entryBundle) - if ok { - return eb.entries.get(key) - } - return nil -} - -// Get will return a list of entries associated with the provided keys. -// A nil will be returned for any key not found. -func (ssl *SkipListStar) Get(keys ...uint64) Entries { - entries := make(Entries, 0, len(keys)) - for _, key := range keys { - entries = append(entries, ssl.get(key)) - } - - return entries -} - -func (ssl *SkipListStar) delete(key uint64) Entry { - normalizedKey := ssl.getNormalizedKey(key) - eb, ok := ssl.sl.Get(normalizedKey)[0].(*entryBundle) - if !ok { - return nil - } - - deleted := eb.entries.delete(key) - if deleted != nil { - ssl.num-- - if len(eb.entries) == 0 { - ssl.sl.Delete(eb.key) - } - } - - return deleted -} - -// Delete will remove the provided keys from the SkipList* and -// return a list of entries that were deleted. -func (ssl *SkipListStar) Delete(keys ...uint64) Entries { - deleted := make(Entries, 0, len(keys)) - for _, key := range keys { - deleted = append(deleted, ssl.delete(key)) - } - - return deleted -} - -func (ssl *SkipListStar) iter(key uint64) *starIterator { - normalizedKey := ssl.getNormalizedKey(key) - iter := ssl.sl.Iter(normalizedKey) - if !iter.Next() { - return &starIterator{ - index: iteratorExhausted, - } - } - - eb := iter.Value().(*entryBundle) - return &starIterator{ - index: eb.entries.search(key) - 1, - entries: eb.entries, - iter: iter, - } -} - -// Iter will return an iterator that will visit every value -// equal to or greater than the provided key. -func (ssl *SkipListStar) Iter(key uint64) Iterator { - return ssl.iter(key) -} - -// Len returns the number of items in the SkipList*. -func (ssl *SkipListStar) Len() uint64 { - return ssl.num -} - -// NewStar will allocate, initialize, and return a new SkipListStar. -// The Skip* list has an node size defined by the provided interface -// parameter. This parameter must be a uint type (uint8, uint16, etc). -func NewStar(ifc interface{}) *SkipListStar { - ssl := &SkipListStar{} - ssl.init(ifc) - return ssl -} diff --git a/slice/skip/skipstar_test.go b/slice/skip/skipstar_test.go deleted file mode 100644 index 6adde73..0000000 --- a/slice/skip/skipstar_test.go +++ /dev/null @@ -1,166 +0,0 @@ -/* -Copyright 2014 Workiva, LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package skip - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestStarInsert(t *testing.T) { - ssl := NewStar(uint8(0)) - - e1 := newMockEntry(7) - e2 := newMockEntry(10) - - result := ssl.Insert(e1, e2) - assert.Equal(t, Entries{nil, nil}, result) - assert.Equal(t, Entries{e1}, ssl.Get(7)) - assert.Equal(t, Entries{e2}, ssl.Get(10)) - assert.Equal(t, Entries{e1, e2}, ssl.Get(7, 10)) - assert.Equal(t, Entries{e1, nil}, ssl.Get(7, 13)) - assert.Equal(t, Entries{e2, nil}, ssl.Get(10, 13)) - assert.Equal(t, uint64(2), ssl.Len()) -} - -func TestStarOverwrite(t *testing.T) { - ssl := NewStar(uint8(0)) - e1 := newMockEntry(7) - e2 := newMockEntry(7) - - result := ssl.Insert(e1) - assert.Equal(t, Entries{nil}, result) - assert.Equal(t, uint64(1), ssl.Len()) - - result = ssl.Insert(e2) - assert.Equal(t, Entries{e1}, result) - assert.Equal(t, uint64(1), ssl.Len()) -} - -func TestStarDelete(t *testing.T) { - ssl := NewStar(uint8(0)) - e1 := newMockEntry(5) - e2 := newMockEntry(10) - ssl.Insert(e1, e2) - - result := ssl.Delete(e1.Key(), e2.Key()) - assert.Equal(t, Entries{e1, e2}, result) - assert.Equal(t, uint64(0), ssl.Len()) -} - -func TestStarIter(t *testing.T) { - ssl := NewStar(uint8(0)) - - iter := ssl.Iter(0) - assert.False(t, iter.Next()) - assert.Nil(t, iter.Value()) - - e1 := newMockEntry(5) - e2 := newMockEntry(10) - ssl.Insert(e1, e2) - - iter = ssl.Iter(0) - assert.Equal(t, Entries{e1, e2}, iter.exhaust()) - - iter = ssl.Iter(5) - assert.Equal(t, Entries{e1, e2}, iter.exhaust()) - - iter = ssl.Iter(6) - assert.Equal(t, Entries{e2}, iter.exhaust()) - - iter = ssl.Iter(10) - assert.Equal(t, Entries{e2}, iter.exhaust()) - - iter = ssl.Iter(11) - assert.Equal(t, Entries{}, iter.exhaust()) -} - -func BenchmarkStarInsert(b *testing.B) { - numItems := b.N - sl := NewStar(uint64(0)) - - entries := generateMockEntries(numItems) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - sl.Insert(entries[i%numItems]) - } -} - -func BenchmarkStarGet(b *testing.B) { - numItems := b.N - sl := NewStar(uint64(0)) - - entries := generateMockEntries(numItems) - sl.Insert(entries...) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - sl.Get(entries[i%numItems].Key()) - } -} - -func BenchmarkStarDelete(b *testing.B) { - numItems := b.N - sl := NewStar(uint64(0)) - - entries := generateMockEntries(numItems) - sl.Insert(entries...) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - sl.Delete(entries[i%numItems].Key()) - } -} - -func BenchmarkIterStar(b *testing.B) { - numItems := b.N - sl := NewStar(uint64(0)) - - entries := generateMockEntries(numItems) - sl.Insert(entries...) - - var iter Iterator - b.ResetTimer() - - for i := 0; i < b.N; i++ { - for iter = sl.Iter(0); iter.Next(); { - } - } -} - -func BenchmarkStarPrepend(b *testing.B) { - numItems := b.N - sl := NewStar(uint64(0)) - - entries := make(Entries, 0, numItems) - for i := b.N; i < b.N+numItems; i++ { - entries = append(entries, newMockEntry(uint64(i))) - } - - sl.Insert(entries...) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - sl.Insert(newMockEntry(uint64(i))) - } -}