x/tools/container/intsets: use root block

The root block was used as a sentinel. This means we always need to
allocate a second block on the heap, even if the set has a few small
elements.

We now use the root block: it is always the block with the smallest
offset. The logic becomes very messy if there is no sentinel; to avoid
this we still use a sentinel (a special singleton block) and return it
in when appropriate in the first, last, next wrappers.

Also adding some benchmarks and making some optimizations:

name                           old time/op    new time/op    delta
Popcount-4                       2.18ns ± 1%    2.21ns ± 1%    +1.47%
InsertProbeSparse_2_10-4         76.2ns ±23%    37.2ns ± 1%   -51.21%
InsertProbeSparse_10_10-4         240ns ±15%     162ns ± 4%   -32.58%
InsertProbeSparse_10_1000-4       419ns ± 4%     371ns ±19%   -11.43%
InsertProbeSparse_100_100-4      2.30µs ± 1%    1.93µs ± 1%   -16.08%
InsertProbeSparse_100_10000-4    2.12µs ± 3%    2.07µs ± 1%    -2.11%
UnionDifferenceSparse-4           165µs ±16%     170µs ± 9%      ~
UnionDifferenceHashTable-4        310µs ±10%     291µs ±17%      ~
AppendTo-4                       11.0µs ± 0%    11.0µs ± 0%    -0.35%

name                           old alloc/op   new alloc/op   delta
Popcount-4                       0.00B ±NaN%    0.00B ±NaN%      ~
InsertProbeSparse_2_10-4          64.0B ± 0%     0.0B ±NaN%  -100.00%
InsertProbeSparse_10_10-4         64.0B ± 0%     0.0B ±NaN%  -100.00%
InsertProbeSparse_10_1000-4        256B ± 0%      192B ± 0%   -25.00%
InsertProbeSparse_100_100-4       64.0B ± 0%     0.0B ±NaN%  -100.00%
InsertProbeSparse_100_10000-4      256B ± 0%      192B ± 0%   -25.00%
UnionDifferenceSparse-4          59.4kB ± 0%    59.2kB ± 0%    -0.32%
UnionDifferenceHashTable-4        138kB ± 0%     138kB ± 0%      ~
AppendTo-4                       0.00B ±NaN%    0.00B ±NaN%      ~

name                           old allocs/op  new allocs/op  delta
Popcount-4                        0.00 ±NaN%     0.00 ±NaN%      ~
InsertProbeSparse_2_10-4           1.00 ± 0%     0.00 ±NaN%  -100.00%
InsertProbeSparse_10_10-4          1.00 ± 0%     0.00 ±NaN%  -100.00%
InsertProbeSparse_10_1000-4        4.00 ± 0%      3.00 ± 0%   -25.00%
InsertProbeSparse_100_100-4        1.00 ± 0%     0.00 ±NaN%  -100.00%
InsertProbeSparse_100_10000-4      4.00 ± 0%      3.00 ± 0%   -25.00%
UnionDifferenceSparse-4             928 ± 0%       925 ± 0%    -0.32%
UnionDifferenceHashTable-4          271 ± 0%       271 ± 0%      ~
AppendTo-4                        0.00 ±NaN%     0.00 ±NaN%      ~

Fixes golang/go#21311.

Change-Id: Ie472a2afa269c21cb33b22ffdac8dd2594b816ac
Reviewed-on: https://go-review.googlesource.com/53431
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Radu Berinde 2017-08-04 21:38:32 -04:00 committed by Alan Donovan
parent 84a35ef54d
commit 43e94ff202
2 changed files with 321 additions and 182 deletions

View File

@ -21,10 +21,6 @@ package intsets // import "golang.org/x/tools/container/intsets"
// The space usage would be proportional to Max(), not Len(), and the // The space usage would be proportional to Max(), not Len(), and the
// implementation would be based upon big.Int. // implementation would be based upon big.Int.
// //
// TODO(adonovan): experiment with making the root block indirect (nil
// iff IsEmpty). This would reduce the memory usage when empty and
// might simplify the aliasing invariants.
//
// TODO(adonovan): opt: make UnionWith and Difference faster. // TODO(adonovan): opt: make UnionWith and Difference faster.
// These are the hot-spots for go/pointer. // These are the hot-spots for go/pointer.
@ -45,9 +41,10 @@ type Sparse struct {
// An uninitialized Sparse represents an empty set. // An uninitialized Sparse represents an empty set.
// An empty set may also be represented by // An empty set may also be represented by
// root.next == root.prev == &root. // root.next == root.prev == &root.
// In a non-empty set, root.next points to the first block and //
// root.prev to the last. // The root is always the block with the smallest offset.
// root.offset and root.bits are unused. // It can be empty, but only if it is the only block; in that case, offset is
// MaxInt (which is not a valid offset).
root block root block
} }
@ -144,7 +141,6 @@ func (b *block) len() int {
// max returns the maximum element of the block. // max returns the maximum element of the block.
// The block must not be empty. // The block must not be empty.
//
func (b *block) max() int { func (b *block) max() int {
bi := b.offset + bitsPerBlock bi := b.offset + bitsPerBlock
// Decrement bi by number of high zeros in last.bits. // Decrement bi by number of high zeros in last.bits.
@ -161,7 +157,6 @@ func (b *block) max() int {
// and also removes it if take is set. // and also removes it if take is set.
// The block must not be initially empty. // The block must not be initially empty.
// NB: may leave the block empty. // NB: may leave the block empty.
//
func (b *block) min(take bool) int { func (b *block) min(take bool) int {
for i, w := range b.bits { for i, w := range b.bits {
if w != 0 { if w != 0 {
@ -204,14 +199,20 @@ func offsetAndBitIndex(x int) (int, uint) {
// -- Sparse -------------------------------------------------------------- // -- Sparse --------------------------------------------------------------
// start returns the root's next block, which is the root block // none is a shared, empty, sentinel block that indicates the end of a block
// (if s.IsEmpty()) or the first true block otherwise. // list.
// start has the side effect of ensuring that s is properly var none block
// initialized.
// // Dummy type used to generate an implicit panic. This must be defined at the
func (s *Sparse) start() *block { // package level; if it is defined inside a function, it prevents the inlining
// of that function.
type to_copy_a_sparse_you_must_call_its_Copy_method struct{}
// init ensures s is properly initialized.
func (s *Sparse) init() {
root := &s.root root := &s.root
if root.next == nil { if root.next == nil {
root.offset = MaxInt
root.next = root root.next = root
root.prev = root root.prev = root
} else if root.next.prev != root { } else if root.next.prev != root {
@ -219,21 +220,45 @@ func (s *Sparse) start() *block {
// new Sparse y shares the old linked list, but iteration // new Sparse y shares the old linked list, but iteration
// on y will never encounter &y.root so it goes into a // on y will never encounter &y.root so it goes into a
// loop. Fail fast before this occurs. // loop. Fail fast before this occurs.
panic("A Sparse has been copied without (*Sparse).Copy()") // We don't want to call panic here because it prevents the
// inlining of this function.
_ = (interface{}(nil)).(to_copy_a_sparse_you_must_call_its_Copy_method)
}
} }
return root.next func (s *Sparse) first() *block {
s.init()
if s.root.offset == MaxInt {
return &none
}
return &s.root
}
// next returns the next block in the list, or end if b is the last block.
func (s *Sparse) next(b *block) *block {
if b.next == &s.root {
return &none
}
return b.next
}
// prev returns the previous block in the list, or end if b is the first block.
func (s *Sparse) prev(b *block) *block {
if b.prev == &s.root {
return &none
}
return b.prev
} }
// IsEmpty reports whether the set s is empty. // IsEmpty reports whether the set s is empty.
func (s *Sparse) IsEmpty() bool { func (s *Sparse) IsEmpty() bool {
return s.start() == &s.root return s.root.next == nil || s.root.offset == MaxInt
} }
// Len returns the number of elements in the set s. // Len returns the number of elements in the set s.
func (s *Sparse) Len() int { func (s *Sparse) Len() int {
var l int var l int
for b := s.start(); b != &s.root; b = b.next { for b := s.first(); b != &none; b = s.next(b) {
l += b.len() l += b.len()
} }
return l return l
@ -252,19 +277,16 @@ func (s *Sparse) Min() int {
if s.IsEmpty() { if s.IsEmpty() {
return MaxInt return MaxInt
} }
return s.root.next.min(false) return s.root.min(false)
} }
// block returns the block that would contain offset, // block returns the block that would contain offset,
// or nil if s contains no such block. // or nil if s contains no such block.
//
func (s *Sparse) block(offset int) *block { func (s *Sparse) block(offset int) *block {
b := s.start() for b := s.first(); b != &none && b.offset <= offset; b = s.next(b) {
for b != &s.root && b.offset <= offset {
if b.offset == offset { if b.offset == offset {
return b return b
} }
b = b.next
} }
return nil return nil
} }
@ -272,26 +294,49 @@ func (s *Sparse) block(offset int) *block {
// Insert adds x to the set s, and reports whether the set grew. // Insert adds x to the set s, and reports whether the set grew.
func (s *Sparse) Insert(x int) bool { func (s *Sparse) Insert(x int) bool {
offset, i := offsetAndBitIndex(x) offset, i := offsetAndBitIndex(x)
b := s.start()
for b != &s.root && b.offset <= offset { b := s.first()
for ; b != &none && b.offset <= offset; b = s.next(b) {
if b.offset == offset { if b.offset == offset {
return b.insert(i) return b.insert(i)
} }
b = b.next
} }
// Insert new block before b. // Insert new block before b.
new := &block{offset: offset} new := s.insertBlockBefore(b)
new.next = b new.offset = offset
new.prev = b.prev
new.prev.next = new
new.next.prev = new
return new.insert(i) return new.insert(i)
} }
func (s *Sparse) removeBlock(b *block) { // removeBlock removes a block and returns the block that followed it (or end if
// it was the last block).
func (s *Sparse) removeBlock(b *block) *block {
if b != &s.root {
b.prev.next = b.next b.prev.next = b.next
b.next.prev = b.prev b.next.prev = b.prev
if b.next == &s.root {
return &none
}
return b.next
}
first := s.root.next
if first == &s.root {
// This was the only block.
s.Clear()
return &none
}
s.root.offset = first.offset
s.root.bits = first.bits
if first.next == &s.root {
// Single block remaining.
s.root.next = &s.root
s.root.prev = &s.root
} else {
s.root.next = first.next
first.next.prev = &s.root
}
return &s.root
} }
// Remove removes x from the set s, and reports whether the set shrank. // Remove removes x from the set s, and reports whether the set shrank.
@ -311,8 +356,11 @@ func (s *Sparse) Remove(x int) bool {
// Clear removes all elements from the set s. // Clear removes all elements from the set s.
func (s *Sparse) Clear() { func (s *Sparse) Clear() {
s.root.next = &s.root s.root = block{
s.root.prev = &s.root offset: MaxInt,
next: &s.root,
prev: &s.root,
}
} }
// If set s is non-empty, TakeMin sets *p to the minimum element of // If set s is non-empty, TakeMin sets *p to the minimum element of
@ -325,13 +373,12 @@ func (s *Sparse) Clear() {
// for worklist.TakeMin(&x) { use(x) } // for worklist.TakeMin(&x) { use(x) }
// //
func (s *Sparse) TakeMin(p *int) bool { func (s *Sparse) TakeMin(p *int) bool {
head := s.start() if s.IsEmpty() {
if head == &s.root {
return false return false
} }
*p = head.min(true) *p = s.root.min(true)
if head.empty() { if s.root.empty() {
s.removeBlock(head) s.removeBlock(&s.root)
} }
return true return true
} }
@ -352,7 +399,7 @@ func (s *Sparse) Has(x int) bool {
// natural control flow with continue/break/return. // natural control flow with continue/break/return.
// //
func (s *Sparse) forEach(f func(int)) { func (s *Sparse) forEach(f func(int)) {
for b := s.start(); b != &s.root; b = b.next { for b := s.first(); b != &none; b = s.next(b) {
b.forEach(f) b.forEach(f)
} }
} }
@ -363,22 +410,51 @@ func (s *Sparse) Copy(x *Sparse) {
return return
} }
xb := x.start() xb := x.first()
sb := s.start() sb := s.first()
for xb != &x.root { for xb != &none {
if sb == &s.root { if sb == &none {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
} }
sb.offset = xb.offset sb.offset = xb.offset
sb.bits = xb.bits sb.bits = xb.bits
xb = xb.next xb = x.next(xb)
sb = sb.next sb = s.next(sb)
} }
s.discardTail(sb) s.discardTail(sb)
} }
// insertBlockBefore returns a new block, inserting it before next. // insertBlockBefore returns a new block, inserting it before next.
// If next is the root, the root is replaced. If next is end, the block is
// inserted at the end.
func (s *Sparse) insertBlockBefore(next *block) *block { func (s *Sparse) insertBlockBefore(next *block) *block {
if s.IsEmpty() {
if next != &none {
panic("BUG: passed block with empty set")
}
return &s.root
}
if next == &s.root {
// Special case: we need to create a new block that will become the root
// block.The old root block becomes the second block.
second := s.root
s.root = block{
next: &second,
}
if second.next == &s.root {
s.root.prev = &second
} else {
s.root.prev = second.prev
second.next.prev = &second
second.prev = &s.root
}
return &s.root
}
if next == &none {
// Insert before root.
next = &s.root
}
b := new(block) b := new(block)
b.next = next b.next = next
b.prev = next.prev b.prev = next.prev
@ -389,11 +465,15 @@ func (s *Sparse) insertBlockBefore(next *block) *block {
// discardTail removes block b and all its successors from s. // discardTail removes block b and all its successors from s.
func (s *Sparse) discardTail(b *block) { func (s *Sparse) discardTail(b *block) {
if b != &s.root { if b != &none {
if b == &s.root {
s.Clear()
} else {
b.prev.next = &s.root b.prev.next = &s.root
s.root.prev = b.prev s.root.prev = b.prev
} }
} }
}
// IntersectionWith sets s to the intersection s ∩ x. // IntersectionWith sets s to the intersection s ∩ x.
func (s *Sparse) IntersectionWith(x *Sparse) { func (s *Sparse) IntersectionWith(x *Sparse) {
@ -401,16 +481,15 @@ func (s *Sparse) IntersectionWith(x *Sparse) {
return return
} }
xb := x.start() xb := x.first()
sb := s.start() sb := s.first()
for xb != &x.root && sb != &s.root { for xb != &none && sb != &none {
switch { switch {
case xb.offset < sb.offset: case xb.offset < sb.offset:
xb = xb.next xb = x.next(xb)
case xb.offset > sb.offset: case xb.offset > sb.offset:
sb = sb.next sb = s.removeBlock(sb)
s.removeBlock(sb.prev)
default: default:
var sum word var sum word
@ -420,12 +499,12 @@ func (s *Sparse) IntersectionWith(x *Sparse) {
sum |= r sum |= r
} }
if sum != 0 { if sum != 0 {
sb = sb.next sb = s.next(sb)
} else { } else {
// sb will be overwritten or removed // sb will be overwritten or removed
} }
xb = xb.next xb = x.next(xb)
} }
} }
@ -446,20 +525,20 @@ func (s *Sparse) Intersection(x, y *Sparse) {
return return
} }
xb := x.start() xb := x.first()
yb := y.start() yb := y.first()
sb := s.start() sb := s.first()
for xb != &x.root && yb != &y.root { for xb != &none && yb != &none {
switch { switch {
case xb.offset < yb.offset: case xb.offset < yb.offset:
xb = xb.next xb = x.next(xb)
continue continue
case xb.offset > yb.offset: case xb.offset > yb.offset:
yb = yb.next yb = y.next(yb)
continue continue
} }
if sb == &s.root { if sb == &none {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
} }
sb.offset = xb.offset sb.offset = xb.offset
@ -471,13 +550,13 @@ func (s *Sparse) Intersection(x, y *Sparse) {
sum |= r sum |= r
} }
if sum != 0 { if sum != 0 {
sb = sb.next sb = s.next(sb)
} else { } else {
// sb will be overwritten or removed // sb will be overwritten or removed
} }
xb = xb.next xb = x.next(xb)
yb = yb.next yb = y.next(yb)
} }
s.discardTail(sb) s.discardTail(sb)
@ -485,22 +564,22 @@ func (s *Sparse) Intersection(x, y *Sparse) {
// Intersects reports whether s ∩ x ≠ ∅. // Intersects reports whether s ∩ x ≠ ∅.
func (s *Sparse) Intersects(x *Sparse) bool { func (s *Sparse) Intersects(x *Sparse) bool {
sb := s.start() sb := s.first()
xb := x.start() xb := x.first()
for sb != &s.root && xb != &x.root { for sb != &none && xb != &none {
switch { switch {
case xb.offset < sb.offset: case xb.offset < sb.offset:
xb = xb.next xb = x.next(xb)
case xb.offset > sb.offset: case xb.offset > sb.offset:
sb = sb.next sb = s.next(sb)
default: default:
for i := range sb.bits { for i := range sb.bits {
if sb.bits[i]&xb.bits[i] != 0 { if sb.bits[i]&xb.bits[i] != 0 {
return true return true
} }
} }
sb = sb.next sb = s.next(sb)
xb = xb.next xb = x.next(xb)
} }
} }
return false return false
@ -513,26 +592,26 @@ func (s *Sparse) UnionWith(x *Sparse) bool {
} }
var changed bool var changed bool
xb := x.start() xb := x.first()
sb := s.start() sb := s.first()
for xb != &x.root { for xb != &none {
if sb != &s.root && sb.offset == xb.offset { if sb != &none && sb.offset == xb.offset {
for i := range xb.bits { for i := range xb.bits {
if sb.bits[i] != xb.bits[i] { if sb.bits[i] != xb.bits[i] {
sb.bits[i] |= xb.bits[i] sb.bits[i] |= xb.bits[i]
changed = true changed = true
} }
} }
xb = xb.next xb = x.next(xb)
} else if sb == &s.root || sb.offset > xb.offset { } else if sb == &none || sb.offset > xb.offset {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
sb.offset = xb.offset sb.offset = xb.offset
sb.bits = xb.bits sb.bits = xb.bits
changed = true changed = true
xb = xb.next xb = x.next(xb)
} }
sb = sb.next sb = s.next(sb)
} }
return changed return changed
} }
@ -551,33 +630,33 @@ func (s *Sparse) Union(x, y *Sparse) {
return return
} }
xb := x.start() xb := x.first()
yb := y.start() yb := y.first()
sb := s.start() sb := s.first()
for xb != &x.root || yb != &y.root { for xb != &none || yb != &none {
if sb == &s.root { if sb == &none {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
} }
switch { switch {
case yb == &y.root || (xb != &x.root && xb.offset < yb.offset): case yb == &none || (xb != &none && xb.offset < yb.offset):
sb.offset = xb.offset sb.offset = xb.offset
sb.bits = xb.bits sb.bits = xb.bits
xb = xb.next xb = x.next(xb)
case xb == &x.root || (yb != &y.root && yb.offset < xb.offset): case xb == &none || (yb != &none && yb.offset < xb.offset):
sb.offset = yb.offset sb.offset = yb.offset
sb.bits = yb.bits sb.bits = yb.bits
yb = yb.next yb = y.next(yb)
default: default:
sb.offset = xb.offset sb.offset = xb.offset
for i := range xb.bits { for i := range xb.bits {
sb.bits[i] = xb.bits[i] | yb.bits[i] sb.bits[i] = xb.bits[i] | yb.bits[i]
} }
xb = xb.next xb = x.next(xb)
yb = yb.next yb = y.next(yb)
} }
sb = sb.next sb = s.next(sb)
} }
s.discardTail(sb) s.discardTail(sb)
@ -590,15 +669,15 @@ func (s *Sparse) DifferenceWith(x *Sparse) {
return return
} }
xb := x.start() xb := x.first()
sb := s.start() sb := s.first()
for xb != &x.root && sb != &s.root { for xb != &none && sb != &none {
switch { switch {
case xb.offset > sb.offset: case xb.offset > sb.offset:
sb = sb.next sb = s.next(sb)
case xb.offset < sb.offset: case xb.offset < sb.offset:
xb = xb.next xb = x.next(xb)
default: default:
var sum word var sum word
@ -607,12 +686,12 @@ func (s *Sparse) DifferenceWith(x *Sparse) {
sb.bits[i] = r sb.bits[i] = r
sum |= r sum |= r
} }
sb = sb.next
xb = xb.next
if sum == 0 { if sum == 0 {
s.removeBlock(sb.prev) sb = s.removeBlock(sb)
} else {
sb = s.next(sb)
} }
xb = x.next(xb)
} }
} }
} }
@ -633,27 +712,27 @@ func (s *Sparse) Difference(x, y *Sparse) {
return return
} }
xb := x.start() xb := x.first()
yb := y.start() yb := y.first()
sb := s.start() sb := s.first()
for xb != &x.root && yb != &y.root { for xb != &none && yb != &none {
if xb.offset > yb.offset { if xb.offset > yb.offset {
// y has block, x has none // y has block, x has &none
yb = yb.next yb = y.next(yb)
continue continue
} }
if sb == &s.root { if sb == &none {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
} }
sb.offset = xb.offset sb.offset = xb.offset
switch { switch {
case xb.offset < yb.offset: case xb.offset < yb.offset:
// x has block, y has none // x has block, y has &none
sb.bits = xb.bits sb.bits = xb.bits
sb = sb.next sb = s.next(sb)
default: default:
// x and y have corresponding blocks // x and y have corresponding blocks
@ -664,25 +743,25 @@ func (s *Sparse) Difference(x, y *Sparse) {
sum |= r sum |= r
} }
if sum != 0 { if sum != 0 {
sb = sb.next sb = s.next(sb)
} else { } else {
// sb will be overwritten or removed // sb will be overwritten or removed
} }
yb = yb.next yb = y.next(yb)
} }
xb = xb.next xb = x.next(xb)
} }
for xb != &x.root { for xb != &none {
if sb == &s.root { if sb == &none {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
} }
sb.offset = xb.offset sb.offset = xb.offset
sb.bits = xb.bits sb.bits = xb.bits
sb = sb.next sb = s.next(sb)
xb = xb.next xb = x.next(xb)
} }
s.discardTail(sb) s.discardTail(sb)
@ -695,17 +774,17 @@ func (s *Sparse) SymmetricDifferenceWith(x *Sparse) {
return return
} }
sb := s.start() sb := s.first()
xb := x.start() xb := x.first()
for xb != &x.root && sb != &s.root { for xb != &none && sb != &none {
switch { switch {
case sb.offset < xb.offset: case sb.offset < xb.offset:
sb = sb.next sb = s.next(sb)
case xb.offset < sb.offset: case xb.offset < sb.offset:
nb := s.insertBlockBefore(sb) nb := s.insertBlockBefore(sb)
nb.offset = xb.offset nb.offset = xb.offset
nb.bits = xb.bits nb.bits = xb.bits
xb = xb.next xb = x.next(xb)
default: default:
var sum word var sum word
for i := range sb.bits { for i := range sb.bits {
@ -713,20 +792,21 @@ func (s *Sparse) SymmetricDifferenceWith(x *Sparse) {
sb.bits[i] = r sb.bits[i] = r
sum |= r sum |= r
} }
sb = sb.next
xb = xb.next
if sum == 0 { if sum == 0 {
s.removeBlock(sb.prev) sb = s.removeBlock(sb)
} else {
sb = s.next(sb)
} }
xb = x.next(xb)
} }
} }
for xb != &x.root { // append the tail of x to s for xb != &none { // append the tail of x to s
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
sb.offset = xb.offset sb.offset = xb.offset
sb.bits = xb.bits sb.bits = xb.bits
sb = sb.next sb = s.next(sb)
xb = xb.next xb = x.next(xb)
} }
} }
@ -744,24 +824,24 @@ func (s *Sparse) SymmetricDifference(x, y *Sparse) {
return return
} }
sb := s.start() sb := s.first()
xb := x.start() xb := x.first()
yb := y.start() yb := y.first()
for xb != &x.root && yb != &y.root { for xb != &none && yb != &none {
if sb == &s.root { if sb == &none {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
} }
switch { switch {
case yb.offset < xb.offset: case yb.offset < xb.offset:
sb.offset = yb.offset sb.offset = yb.offset
sb.bits = yb.bits sb.bits = yb.bits
sb = sb.next sb = s.next(sb)
yb = yb.next yb = y.next(yb)
case xb.offset < yb.offset: case xb.offset < yb.offset:
sb.offset = xb.offset sb.offset = xb.offset
sb.bits = xb.bits sb.bits = xb.bits
sb = sb.next sb = s.next(sb)
xb = xb.next xb = x.next(xb)
default: default:
var sum word var sum word
for i := range sb.bits { for i := range sb.bits {
@ -771,31 +851,31 @@ func (s *Sparse) SymmetricDifference(x, y *Sparse) {
} }
if sum != 0 { if sum != 0 {
sb.offset = xb.offset sb.offset = xb.offset
sb = sb.next sb = s.next(sb)
} }
xb = xb.next xb = x.next(xb)
yb = yb.next yb = y.next(yb)
} }
} }
for xb != &x.root { // append the tail of x to s for xb != &none { // append the tail of x to s
if sb == &s.root { if sb == &none {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
} }
sb.offset = xb.offset sb.offset = xb.offset
sb.bits = xb.bits sb.bits = xb.bits
sb = sb.next sb = s.next(sb)
xb = xb.next xb = x.next(xb)
} }
for yb != &y.root { // append the tail of y to s for yb != &none { // append the tail of y to s
if sb == &s.root { if sb == &none {
sb = s.insertBlockBefore(sb) sb = s.insertBlockBefore(sb)
} }
sb.offset = yb.offset sb.offset = yb.offset
sb.bits = yb.bits sb.bits = yb.bits
sb = sb.next sb = s.next(sb)
yb = yb.next yb = y.next(yb)
} }
s.discardTail(sb) s.discardTail(sb)
@ -807,22 +887,22 @@ func (s *Sparse) SubsetOf(x *Sparse) bool {
return true return true
} }
sb := s.start() sb := s.first()
xb := x.start() xb := x.first()
for sb != &s.root { for sb != &none {
switch { switch {
case xb == &x.root || xb.offset > sb.offset: case xb == &none || xb.offset > sb.offset:
return false return false
case xb.offset < sb.offset: case xb.offset < sb.offset:
xb = xb.next xb = x.next(xb)
default: default:
for i := range sb.bits { for i := range sb.bits {
if sb.bits[i]&^xb.bits[i] != 0 { if sb.bits[i]&^xb.bits[i] != 0 {
return false return false
} }
} }
sb = sb.next sb = s.next(sb)
xb = xb.next xb = x.next(xb)
} }
} }
return true return true
@ -833,13 +913,13 @@ func (s *Sparse) Equals(t *Sparse) bool {
if s == t { if s == t {
return true return true
} }
sb := s.start() sb := s.first()
tb := t.start() tb := t.first()
for { for {
switch { switch {
case sb == &s.root && tb == &t.root: case sb == &none && tb == &none:
return true return true
case sb == &s.root || tb == &t.root: case sb == &none || tb == &none:
return false return false
case sb.offset != tb.offset: case sb.offset != tb.offset:
return false return false
@ -847,8 +927,8 @@ func (s *Sparse) Equals(t *Sparse) bool {
return false return false
} }
sb = sb.next sb = s.next(sb)
tb = tb.next tb = t.next(tb)
} }
} }
@ -913,7 +993,7 @@ func (s *Sparse) BitString() string {
// //
func (s *Sparse) GoString() string { func (s *Sparse) GoString() string {
var buf bytes.Buffer var buf bytes.Buffer
for b := s.start(); b != &s.root; b = b.next { for b := s.first(); b != &none; b = s.next(b) {
fmt.Fprintf(&buf, "block %p {offset=%d next=%p prev=%p", fmt.Fprintf(&buf, "block %p {offset=%d next=%p prev=%p",
b, b.offset, b.next, b.prev) b, b.offset, b.next, b.prev)
for _, w := range b.bits { for _, w := range b.bits {
@ -937,13 +1017,18 @@ func (s *Sparse) AppendTo(slice []int) []int {
// check returns an error if the representation invariants of s are violated. // check returns an error if the representation invariants of s are violated.
func (s *Sparse) check() error { func (s *Sparse) check() error {
if !s.root.empty() { s.init()
return fmt.Errorf("non-empty root block") if s.root.empty() {
// An empty set must have only the root block with offset MaxInt.
if s.root.next != &s.root {
return fmt.Errorf("multiple blocks with empty root block")
} }
if s.root.offset != 0 { if s.root.offset != MaxInt {
return fmt.Errorf("root block has non-zero offset %d", s.root.offset) return fmt.Errorf("empty set has offset %d, should be MaxInt", s.root.offset)
} }
for b := s.start(); b != &s.root; b = b.next { return nil
}
for b := s.first(); ; b = s.next(b) {
if b.offset%bitsPerBlock != 0 { if b.offset%bitsPerBlock != 0 {
return fmt.Errorf("bad offset modulo: %d", b.offset) return fmt.Errorf("bad offset modulo: %d", b.offset)
} }
@ -956,11 +1041,12 @@ func (s *Sparse) check() error {
if b.next.prev != b { if b.next.prev != b {
return fmt.Errorf("bad next.prev link") return fmt.Errorf("bad next.prev link")
} }
if b.prev != &s.root { if b.next == &s.root {
if b.offset <= b.prev.offset { break
return fmt.Errorf("bad offset order: b.offset=%d, prev.offset=%d",
b.offset, b.prev.offset)
} }
if b.offset >= b.next.offset {
return fmt.Errorf("bad offset order: b.offset=%d, b.next.offset=%d",
b.offset, b.next.offset)
} }
} }
return nil return nil

View File

@ -471,7 +471,7 @@ func TestIntersects(t *testing.T) {
z.IntersectionWith(y) z.IntersectionWith(y)
if got, want := x.Intersects(y), !z.IsEmpty(); got != want { if got, want := x.Intersects(y), !z.IsEmpty(); got != want {
t.Errorf("Intersects: got %v, want %v", got, want) t.Errorf("Intersects(%s, %s): got %v, want %v (%s)", x, y, got, want, &z)
} }
// make it false // make it false
@ -563,7 +563,7 @@ func TestFailFastOnShallowCopy(t *testing.T) {
y := x // shallow copy (breaks representation invariants) y := x // shallow copy (breaks representation invariants)
defer func() { defer func() {
got := fmt.Sprint(recover()) got := fmt.Sprint(recover())
want := "A Sparse has been copied without (*Sparse).Copy()" want := "interface conversion: interface {} is nil, not intsets.to_copy_a_sparse_you_must_call_its_Copy_method"
if got != want { if got != want {
t.Errorf("shallow copy: recover() = %q, want %q", got, want) t.Errorf("shallow copy: recover() = %q, want %q", got, want)
} }
@ -579,7 +579,60 @@ func TestFailFastOnShallowCopy(t *testing.T) {
// - Gather set distributions from pointer analysis. // - Gather set distributions from pointer analysis.
// - Measure memory usage. // - Measure memory usage.
func BenchmarkSparseBitVector(b *testing.B) { func benchmarkInsertProbeSparse(b *testing.B, size, spread int) {
prng := rand.New(rand.NewSource(0))
// Generate our insertions and probes beforehand (we don't want to benchmark
// the prng).
insert := make([]int, size)
probe := make([]int, size*2)
for i := range insert {
insert[i] = prng.Int() % spread
}
for i := range probe {
probe[i] = prng.Int() % spread
}
b.ResetTimer()
var x intsets.Sparse
for tries := 0; tries < b.N; tries++ {
x.Clear()
for _, n := range insert {
x.Insert(n)
}
hits := 0
for _, n := range probe {
if x.Has(n) {
hits++
}
}
// Use the variable so it doesn't get optimized away.
if hits > len(probe) {
b.Fatalf("%d hits, only %d probes", hits, len(probe))
}
}
}
func BenchmarkInsertProbeSparse_2_10(b *testing.B) {
benchmarkInsertProbeSparse(b, 2, 10)
}
func BenchmarkInsertProbeSparse_10_10(b *testing.B) {
benchmarkInsertProbeSparse(b, 10, 10)
}
func BenchmarkInsertProbeSparse_10_1000(b *testing.B) {
benchmarkInsertProbeSparse(b, 10, 1000)
}
func BenchmarkInsertProbeSparse_100_100(b *testing.B) {
benchmarkInsertProbeSparse(b, 100, 100)
}
func BenchmarkInsertProbeSparse_100_10000(b *testing.B) {
benchmarkInsertProbeSparse(b, 100, 1000)
}
func BenchmarkUnionDifferenceSparse(b *testing.B) {
prng := rand.New(rand.NewSource(0)) prng := rand.New(rand.NewSource(0))
for tries := 0; tries < b.N; tries++ { for tries := 0; tries < b.N; tries++ {
var x, y, z intsets.Sparse var x, y, z intsets.Sparse
@ -596,7 +649,7 @@ func BenchmarkSparseBitVector(b *testing.B) {
} }
} }
func BenchmarkHashTable(b *testing.B) { func BenchmarkUnionDifferenceHashTable(b *testing.B) {
prng := rand.New(rand.NewSource(0)) prng := rand.New(rand.NewSource(0))
for tries := 0; tries < b.N; tries++ { for tries := 0; tries < b.N; tries++ {
x, y, z := make(map[int]bool), make(map[int]bool), make(map[int]bool) x, y, z := make(map[int]bool), make(map[int]bool), make(map[int]bool)