Skip to content

Commit

Permalink
Merge pull request #21 from gconnell/master
Browse files Browse the repository at this point in the history
Add a Clear method that clears all nodes into a freelist.
  • Loading branch information
gconnell authored Jan 24, 2018
2 parents 316fb6d + a7570a4 commit e89373f
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 3 deletions.
66 changes: 63 additions & 3 deletions btree.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,16 @@ func (f *FreeList) newNode() (n *node) {
return
}

func (f *FreeList) freeNode(n *node) {
// freeNode adds the given node to the list, returning true if it was added
// and false if it was discarded.
func (f *FreeList) freeNode(n *node) (out bool) {
f.mu.Lock()
if len(f.freelist) < cap(f.freelist) {
f.freelist = append(f.freelist, n)
out = true
}
f.mu.Unlock()
return
}

// ItemIterator allows callers of Ascend* to iterate in-order over portions of
Expand Down Expand Up @@ -635,13 +639,30 @@ func (c *copyOnWriteContext) newNode() (n *node) {
return
}

func (c *copyOnWriteContext) freeNode(n *node) {
type freeType int

const (
ftFreelistFull freeType = iota // node was freed (available for GC, not stored in freelist)
ftStored // node was stored in the freelist for later use
ftNotOwned // node was ignored by COW, since it's owned by another one
)

// freeNode frees a node within a given COW context, if it's owned by that
// context. It returns what happened to the node (see freeType const
// documentation).
func (c *copyOnWriteContext) freeNode(n *node) freeType {
if n.cow == c {
// clear to allow GC
n.items.truncate(0)
n.children.truncate(0)
n.cow = nil
c.freelist.freeNode(n)
if c.freelist.freeNode(n) {
return ftStored
} else {
return ftFreelistFull
}
} else {
return ftNotOwned
}
}

Expand Down Expand Up @@ -812,6 +833,45 @@ func (t *BTree) Len() int {
return t.length
}

// Clear removes all items from the btree. If addNodesToFreelist is true,
// t's nodes are added to its freelist as part of this call, until the freelist
// is full. Otherwise, the root node is simply dereferenced and the subtree
// left to Go's normal GC processes.
//
// This can be much faster
// than calling Delete on all elements, because that requires finding/removing
// each element in the tree and updating the tree accordingly. It also is
// somewhat faster than creating a new tree to replace the old one, because
// nodes from the old tree are reclaimed into the freelist for use by the new
// one, instead of being lost to the garbage collector.
//
// This call takes:
// O(1): when addNodesToFreelist is false, this is a single operation.
// O(1): when the freelist is already full, it breaks out immediately
// O(freelist size): when the freelist is empty and the nodes are all owned
// by this tree, nodes are added to the freelist until full.
// O(tree size): when all nodes are owned by another tree, all nodes are
// iterated over looking for nodes to add to the freelist, and due to
// ownership, none are.
func (t *BTree) Clear(addNodesToFreelist bool) {
if t.root != nil && addNodesToFreelist {
t.root.reset(t.cow)
}
t.root, t.length = nil, 0
}

// reset returns a subtree to the freelist. It breaks out immediately if the
// freelist is full, since the only benefit of iterating is to fill that
// freelist up. Returns true if parent reset call should continue.
func (n *node) reset(c *copyOnWriteContext) bool {
for _, child := range n.children {
if !child.reset(c) {
return false
}
}
return c.freeNode(n) != ftFreelistFull
}

// Int implements the Item interface for integers.
type Int int

Expand Down
81 changes: 81 additions & 0 deletions btree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,3 +687,84 @@ func TestCloneConcurrentOperations(t *testing.T) {
}
}
}

func BenchmarkDeleteAndRestore(b *testing.B) {
items := perm(16392)
b.ResetTimer()
b.Run(`CopyBigFreeList`, func(b *testing.B) {
fl := NewFreeList(16392)
tr := NewWithFreeList(*btreeDegree, fl)
for _, v := range items {
tr.ReplaceOrInsert(v)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
dels := make([]Item, 0, tr.Len())
tr.Ascend(ItemIterator(func(b Item) bool {
dels = append(dels, b)
return true
}))
for _, del := range dels {
tr.Delete(del)
}
// tr is now empty, we make a new empty copy of it.
tr = NewWithFreeList(*btreeDegree, fl)
for _, v := range items {
tr.ReplaceOrInsert(v)
}
}
})
b.Run(`Copy`, func(b *testing.B) {
tr := New(*btreeDegree)
for _, v := range items {
tr.ReplaceOrInsert(v)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
dels := make([]Item, 0, tr.Len())
tr.Ascend(ItemIterator(func(b Item) bool {
dels = append(dels, b)
return true
}))
for _, del := range dels {
tr.Delete(del)
}
// tr is now empty, we make a new empty copy of it.
tr = New(*btreeDegree)
for _, v := range items {
tr.ReplaceOrInsert(v)
}
}
})
b.Run(`ClearBigFreelist`, func(b *testing.B) {
fl := NewFreeList(16392)
tr := NewWithFreeList(*btreeDegree, fl)
for _, v := range items {
tr.ReplaceOrInsert(v)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
tr.Clear(true)
for _, v := range items {
tr.ReplaceOrInsert(v)
}
}
})
b.Run(`Clear`, func(b *testing.B) {
tr := New(*btreeDegree)
for _, v := range items {
tr.ReplaceOrInsert(v)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
tr.Clear(true)
for _, v := range items {
tr.ReplaceOrInsert(v)
}
}
})
}

0 comments on commit e89373f

Please sign in to comment.