mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
Abstract away access to container slices
On a 64-bit machine, the slices in a Container consume 72 bytes, and the Container itself is 80. But we only use one slice at a time! This patch shifts us to keeping a single slice in the Container, and converting provided slices to and from that type when we want to update it. (It is not safe to access the slice through the wrong type.) We also add some new tests, conditional on a build tag called `roaringparanoia`. These tests will be optimized away entirely by the compiler when the tag isn't present, because the conditionals use a const. These catch possible errors like trying to access the bitmap slice of a non-bitmap container. We also eliminate all direct creation of Container literals, so we can mess with the internals more. (On reflection and study, we decided not to go to the fancier design where references to .n and .typ were also converted to function calls, which would have allowed packing those attributes more tightly, because it was a lot more overhead and a lot of work to keep track of.) There's some circumstances where we appear to have been relying on incorrect guesses about the nature of containers. For instance, in xorBitmapRun, there's logic that makes sense only if the output's a run container, but it's not, it's a bitmap container. This creates strange behavior sometimes, though. Several of these are corrected now.
This commit is contained in:
parent
86ea040639
commit
47dcb5b4a7
11 changed files with 1124 additions and 1031 deletions
|
|
@ -93,8 +93,8 @@ type updater struct {
|
|||
mapped bool
|
||||
}
|
||||
|
||||
func (btc *bTreeContainers) PutContainerValues(key uint64, containerType byte, n int, mapped bool) {
|
||||
a := updater{key, int32(n), containerType, mapped}
|
||||
func (btc *bTreeContainers) PutContainerValues(key uint64, typ byte, n int, mapped bool) {
|
||||
a := updater{key, int32(n), typ, mapped}
|
||||
btc.tree.Put(key, a.update)
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ func (btc *bTreeContainers) GetOrCreate(key uint64) *roaring.Container {
|
|||
btc.lastKey = key
|
||||
v, ok := btc.tree.Get(key)
|
||||
if !ok {
|
||||
cont := roaring.NewContainer()
|
||||
cont := roaring.NewContainerArray(nil)
|
||||
btc.tree.Set(key, cont)
|
||||
btc.lastContainer = cont
|
||||
return cont
|
||||
|
|
|
|||
182
roaring/container_slice.go
Normal file
182
roaring/container_slice.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package roaring
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Container represents a Container for uint16 integers.
|
||||
//
|
||||
// These are used for storing the low bits of numbers in larger sets of uint64.
|
||||
// The high bits are stored in a Container's key which is tracked by a separate
|
||||
// data structure. Integers in a Container can be encoded in one of three ways -
|
||||
// the encoding used is usually whichever is most compact, though any Container
|
||||
// type should be able to encode any set of integers safely. For containers with
|
||||
// less than 4,096 values, an array is often used. Containers with long runs of
|
||||
// integers would use run length encoding, and more random data usually uses
|
||||
// bitmap encoding.
|
||||
type Container struct {
|
||||
oneSlice []uint16 // and here is where the magic happens
|
||||
n int32 // number of integers in container
|
||||
mapped bool // mapped directly to a byte slice when true
|
||||
typ byte // array, bitmap, or run
|
||||
}
|
||||
|
||||
// NewContainer returns a new instance of container. This trivial function
|
||||
// may later become more interesting.
|
||||
func NewContainer() *Container {
|
||||
statsHit("NewContainer")
|
||||
return &Container{typ: containerArray}
|
||||
}
|
||||
|
||||
// NewContainerBitmap makes a bitmap container using the provided bitmap, or
|
||||
// an empty one if provided bitmap is nil. If the provided bitmap is too short,
|
||||
// it will be padded.
|
||||
func NewContainerBitmap(n int32, bitmap []uint64) *Container {
|
||||
if bitmap == nil {
|
||||
bitmap = make([]uint64, bitmapN)
|
||||
}
|
||||
// pad to required length
|
||||
if len(bitmap) < bitmapN {
|
||||
bm2 := make([]uint64, bitmapN)
|
||||
copy(bm2, bitmap)
|
||||
bitmap = bm2
|
||||
}
|
||||
return &Container{typ: containerBitmap, n: n, oneSlice: *(*[]uint16)(unsafe.Pointer(&bitmap))}
|
||||
}
|
||||
|
||||
// NewContainerArray returns an array using the provided set of values. It's
|
||||
// okay if the slice is nil; that's a length of zero.
|
||||
func NewContainerArray(set []uint16) *Container {
|
||||
return &Container{typ: containerArray, n: int32(len(set)), oneSlice: *(*[]uint16)(unsafe.Pointer(&set))}
|
||||
|
||||
}
|
||||
|
||||
// NewContainerRun creates a new run array using a provided (possibly nil)
|
||||
// slice of intervals.
|
||||
func NewContainerRun(set []interval16) *Container {
|
||||
c := &Container{typ: containerRun, oneSlice: *(*[]uint16)(unsafe.Pointer(&set))}
|
||||
for _, run := range set {
|
||||
c.n += int32(run.last-run.start) + 1
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// array yields the data viewed as a slice of intervals.
|
||||
func (c *Container) array() []uint16 {
|
||||
if roaringParanoia {
|
||||
if c.typ != containerArray {
|
||||
panic("attempt to read non-array's array")
|
||||
}
|
||||
}
|
||||
return *(*[]uint16)(unsafe.Pointer(&c.oneSlice))
|
||||
}
|
||||
|
||||
// setArray stores a set of uint16s as data.
|
||||
func (c *Container) setArray(array []uint16) {
|
||||
if roaringParanoia {
|
||||
if c.typ != containerArray {
|
||||
panic("attempt to write non-array's array")
|
||||
}
|
||||
}
|
||||
c.oneSlice = *(*[]uint16)(unsafe.Pointer(&array))
|
||||
}
|
||||
|
||||
// bitmap yields the data viewed as a slice of uint64s holding bits.
|
||||
func (c *Container) bitmap() []uint64 {
|
||||
if roaringParanoia {
|
||||
if c.typ != containerBitmap {
|
||||
panic("attempt to read non-bitmap's bitmap")
|
||||
}
|
||||
}
|
||||
return *(*[]uint64)(unsafe.Pointer(&c.oneSlice))
|
||||
}
|
||||
|
||||
// setBitmap stores a set of uint64s as data.
|
||||
func (c *Container) setBitmap(bitmap []uint64) {
|
||||
if roaringParanoia {
|
||||
if c.typ != containerBitmap {
|
||||
panic("attempt to write non-bitmap's bitmap")
|
||||
}
|
||||
}
|
||||
c.oneSlice = *(*[]uint16)(unsafe.Pointer(&bitmap))
|
||||
}
|
||||
|
||||
// runs yields the data viewed as a slice of intervals.
|
||||
func (c *Container) runs() []interval16 {
|
||||
if roaringParanoia {
|
||||
if c.typ != containerRun {
|
||||
panic("attempt to read non-run's runs")
|
||||
}
|
||||
}
|
||||
return *(*[]interval16)(unsafe.Pointer(&c.oneSlice))
|
||||
}
|
||||
|
||||
// setRuns stores a set of intervals as data.
|
||||
func (c *Container) setRuns(runs []interval16) {
|
||||
if roaringParanoia {
|
||||
if c.typ != containerRun {
|
||||
panic("attempt to write non-run's runs")
|
||||
}
|
||||
}
|
||||
c.oneSlice = *(*[]uint16)(unsafe.Pointer(&runs))
|
||||
}
|
||||
|
||||
// Mapped returns true if the container is mapped directly to a byte slice
|
||||
func (c *Container) Mapped() bool {
|
||||
return c.mapped
|
||||
}
|
||||
|
||||
// N returns the cached bit count of the container
|
||||
func (c *Container) N() int32 {
|
||||
return c.n
|
||||
}
|
||||
|
||||
// Update updates the container
|
||||
func (c *Container) Update(typ byte, n int32, mapped bool) {
|
||||
c.typ = typ
|
||||
c.n = n
|
||||
c.mapped = mapped
|
||||
// we don't know that any existing slice is usable, so let's ditch it
|
||||
c.oneSlice = nil
|
||||
}
|
||||
|
||||
// isArray returns true if the container is an array container.
|
||||
func (c *Container) isArray() bool {
|
||||
return c.typ == containerArray
|
||||
}
|
||||
|
||||
// isBitmap returns true if the container is a bitmap container.
|
||||
func (c *Container) isBitmap() bool {
|
||||
return c.typ == containerBitmap
|
||||
}
|
||||
|
||||
// isRun returns true if the container is a run-length-encoded container.
|
||||
func (c *Container) isRun() bool {
|
||||
return c.typ == containerRun
|
||||
}
|
||||
|
||||
// unmap creates copies of the containers data in the heap.
|
||||
//
|
||||
// This is performed when altering the container since its contents could be
|
||||
// pointing at a read-only mmap.
|
||||
func (c *Container) unmap() {
|
||||
if !c.mapped {
|
||||
return
|
||||
}
|
||||
|
||||
switch c.typ {
|
||||
case containerArray:
|
||||
tmp := make([]uint16, len(c.array()))
|
||||
copy(tmp, c.array())
|
||||
c.setArray(tmp)
|
||||
case containerBitmap:
|
||||
tmp := make([]uint64, len(c.bitmap()))
|
||||
copy(tmp, c.bitmap())
|
||||
c.setBitmap(tmp)
|
||||
case containerRun:
|
||||
tmp := make([]interval16, len(c.runs()))
|
||||
copy(tmp, c.runs())
|
||||
c.setRuns(tmp)
|
||||
}
|
||||
c.mapped = false
|
||||
}
|
||||
|
|
@ -46,17 +46,17 @@ func (sc *sliceContainers) Put(key uint64, c *Container) {
|
|||
|
||||
}
|
||||
|
||||
func (sc *sliceContainers) PutContainerValues(key uint64, containerType byte, n int, mapped bool) {
|
||||
func (sc *sliceContainers) PutContainerValues(key uint64, typ byte, n int, mapped bool) {
|
||||
i := search64(sc.keys, key)
|
||||
if i < 0 {
|
||||
c := NewContainer()
|
||||
c.containerType = containerType
|
||||
c.typ = typ
|
||||
c.n = int32(n)
|
||||
c.mapped = mapped
|
||||
sc.insertAt(key, c, -i-1)
|
||||
} else {
|
||||
c := sc.containers[i]
|
||||
c.containerType = containerType
|
||||
c.typ = typ
|
||||
c.n = int32(n)
|
||||
c.mapped = mapped
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ func (sc *sliceContainers) GetOrCreate(key uint64) *Container {
|
|||
sc.lastKey = key
|
||||
i := search64(sc.keys, key)
|
||||
if i < 0 {
|
||||
c := NewContainer()
|
||||
c := NewContainerArray(nil)
|
||||
sc.insertAt(key, c, -i-1)
|
||||
sc.lastContainer = c
|
||||
return c
|
||||
|
|
|
|||
|
|
@ -72,24 +72,26 @@ func (btc *bTreeContainers) Put(key uint64, c *Container) {
|
|||
func (u updater) update(oldV *Container, exists bool) (*Container, bool) {
|
||||
// update the existing container
|
||||
if exists {
|
||||
oldV.Update(u.containerType, u.n, u.mapped)
|
||||
oldV.Update(u.typ, u.n, u.mapped)
|
||||
return oldV, false
|
||||
}
|
||||
cont := NewContainer()
|
||||
cont.Update(u.containerType, u.n, u.mapped)
|
||||
cont.typ = u.typ
|
||||
cont.n = u.n
|
||||
cont.mapped = u.mapped
|
||||
return cont, true
|
||||
}
|
||||
|
||||
// this struct is added to prevent the closure locals from being escaped out to the heap
|
||||
type updater struct {
|
||||
key uint64
|
||||
n int32
|
||||
containerType byte
|
||||
mapped bool
|
||||
key uint64
|
||||
n int32
|
||||
typ byte
|
||||
mapped bool
|
||||
}
|
||||
|
||||
func (btc *bTreeContainers) PutContainerValues(key uint64, containerType byte, n int, mapped bool) {
|
||||
a := updater{key, int32(n), containerType, mapped}
|
||||
func (btc *bTreeContainers) PutContainerValues(key uint64, typ byte, n int, mapped bool) {
|
||||
a := updater{key, int32(n), typ, mapped}
|
||||
btc.tree.Put(key, a.update)
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +108,7 @@ func (btc *bTreeContainers) GetOrCreate(key uint64) *Container {
|
|||
btc.lastKey = key
|
||||
v, ok := btc.tree.Get(key)
|
||||
if !ok {
|
||||
cont := NewContainer()
|
||||
cont := NewContainerArray(nil)
|
||||
btc.tree.Set(key, cont)
|
||||
btc.lastContainer = cont
|
||||
return cont
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ func testContainersIterator(cs Containers, t *testing.T) {
|
|||
t.Fatal("Next() should be false for empty btc")
|
||||
}
|
||||
|
||||
cs.Put(1, &Container{n: 1})
|
||||
cs.Put(2, &Container{n: 2})
|
||||
cs.Put(1, NewContainerArray([]uint16{1}))
|
||||
cs.Put(2, NewContainerArray([]uint16{1, 2}))
|
||||
|
||||
itr, found = cs.Iterator(0)
|
||||
if found {
|
||||
|
|
@ -57,9 +57,9 @@ func testContainersIterator(cs Containers, t *testing.T) {
|
|||
t.Fatalf("itr should be done, but got true")
|
||||
}
|
||||
|
||||
cs.Put(3, &Container{n: 3})
|
||||
cs.Put(5, &Container{n: 5})
|
||||
cs.Put(6, &Container{n: 6})
|
||||
cs.Put(3, NewContainerArray([]uint16{1, 2, 3}))
|
||||
cs.Put(5, NewContainerArray([]uint16{1, 2, 3, 4, 5}))
|
||||
cs.Put(6, NewContainerArray([]uint16{1, 2, 3, 4, 5, 6}))
|
||||
|
||||
itr, found = cs.Iterator(3)
|
||||
if !itr.Next() {
|
||||
|
|
|
|||
1189
roaring/roaring.go
1189
roaring/roaring.go
File diff suppressed because it is too large
Load diff
|
|
@ -248,22 +248,18 @@ type testOp struct {
|
|||
exp string
|
||||
}
|
||||
|
||||
func doContainer(containerType byte, data interface{}) *Container {
|
||||
c := &Container{
|
||||
containerType: containerType,
|
||||
}
|
||||
|
||||
switch containerType {
|
||||
func doContainer(typ byte, data interface{}) *Container {
|
||||
switch typ {
|
||||
case containerArray:
|
||||
c.array = data.([]uint16)
|
||||
return NewContainerArray(data.([]uint16))
|
||||
case containerBitmap:
|
||||
c.bitmap = data.([]uint64)
|
||||
c := NewContainerBitmap(0, data.([]uint64))
|
||||
c.n = c.count()
|
||||
return c
|
||||
case containerRun:
|
||||
c.runs = data.([]interval16)
|
||||
return NewContainerRun(data.([]interval16))
|
||||
}
|
||||
c.n = c.count()
|
||||
|
||||
return c
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupContainerTests() map[byte]map[string]*Container {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
5
roaring/roaring_nop_paranoia.go
Normal file
5
roaring/roaring_nop_paranoia.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// +build !roaringparanoia
|
||||
|
||||
package roaring
|
||||
|
||||
const roaringParanoia = false
|
||||
5
roaring/roaring_paranoia.go
Normal file
5
roaring/roaring_paranoia.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// +build roaringparanoia
|
||||
|
||||
package roaring
|
||||
|
||||
const roaringParanoia = true
|
||||
|
|
@ -435,14 +435,14 @@ func TestBitmap_UnionInPlace1(t *testing.T) {
|
|||
|
||||
result.UnionInPlace(bm0, bm1)
|
||||
if n := result.Count(); n != 2682675 {
|
||||
t.Fatalf("unexpected n: %d", n)
|
||||
t.Fatalf("unexpected n: got %d, expected 2682675", n)
|
||||
}
|
||||
|
||||
bm := testBM()
|
||||
result = roaring.NewBitmap()
|
||||
result.UnionInPlace(bm, bm0)
|
||||
if n := result.Count(); n != 75009 {
|
||||
t.Fatalf("unexpected n: %d", n)
|
||||
t.Fatalf("unexpected n: got %d, expected 75009", n)
|
||||
}
|
||||
|
||||
result = roaring.NewBitmap()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue