use compact node arrays

although this does reduce the amount of memory allocated during Set by
more than half, it doubles the number of allocations and doesn't seem
to impact the runtime. frustrating.
master
magical 2022-01-23 03:08:19 +00:00
parent 6de290647d
commit 82711582b1
2 changed files with 77 additions and 26 deletions

86
pmap.go
View File

@ -29,10 +29,18 @@ type pmap struct {
// A Map implemented as a hashed trie
type node struct {
child [nodeDegree]interface{}
child []interface{}
bitmap uint32
}
func bitmask(shiftedHash uint32) uint32 {
return uint32(1) << (shiftedHash & nodeMask)
}
func (n *node) index(mask uint32) int {
return bits.OnesCount32(n.bitmap & (mask - 1))
}
type collision struct {
hash uint32
leaf []leaf
@ -77,18 +85,28 @@ func (p pmap) Del(k Key) Map {
return p
}
func (m *node) getNode(shift, hash uint32, key Key) interface{} {
i := hash >> shift & nodeMask
return m.child[i]
func (n *node) check() {
if bits.OnesCount32(n.bitmap) != len(n.child) {
panic(fmt.Sprintf("pmap: corrupt bitmap b=%#b len=%d", n.bitmap, len(n.child)))
}
}
func (m collision) getNode(hash uint32, key Key) interface{} {
if hash != m.hash {
func (n *node) getNode(shift, hash uint32, key Key) interface{} {
n.check()
m := bitmask(hash >> shift)
if n.bitmap&m == 0 {
return nil
}
for i := range m.leaf {
if key == m.leaf[i].k {
return m.leaf[i]
return n.child[n.index(m)]
}
func (n collision) getNode(hash uint32, key Key) interface{} {
if hash != n.hash {
return nil
}
for i := range n.leaf {
if key == n.leaf[i].k {
return n.leaf[i]
}
}
return nil
@ -123,9 +141,10 @@ func singleton(key Key, val Value, hash, shift uint32) *node {
func newnode(child interface{}, hash, shift uint32) *node {
n := &node{}
idx := hash >> shift & nodeMask
n.child[idx] = child
n.bitmap = 1 << idx
a := [1]interface{}{child}
n.child = a[:]
n.bitmap = bitmask(hash >> shift)
n.check()
return n
}
@ -155,8 +174,8 @@ func insert(n interface{}, hash uint32, key Key, val Value, hashFn HashFunc) (ne
}
// not a collision, so we must still have some hash bits left
// split the trie
m := newnode(n, h, shift)
return _insert(m, shift)
x := newnode(n, h, shift)
return _insert(x, shift)
}
case *node:
c := n.getNode(shift, hash, key)
@ -164,28 +183,42 @@ func insert(n interface{}, hash uint32, key Key, val Value, hashFn HashFunc) (ne
// new node
c = leaf{key, val}
added = true
m := bitmask(hash >> shift)
x := &node{bitmap: n.bitmap | m}
i := x.index(m)
x.child = make([]interface{}, len(n.child)+1)
copy(x.child[:i], n.child[:i])
x.child[i] = c
copy(x.child[i+1:], n.child[i:])
x.check()
return x
} else {
c = _insert(c, shift+nodeShift)
}
idx := hash >> shift & nodeMask
x := &node{child: n.child, bitmap: n.bitmap}
x.child[idx] = c
x.bitmap |= 1 << idx
// TODO: short circuit if c unchanged?
m := bitmask(hash >> shift)
x := &node{bitmap: n.bitmap}
i := x.index(m)
x.child = make([]interface{}, len(n.child))
copy(x.child, n.child)
x.child[i] = c
x.check()
return x
}
case collision:
if n.hash != hash {
// not a collision, so we must still have some hash bits left
// split the trie
m := newnode(n, n.hash, shift)
return _insert(m, shift)
x := newnode(n, n.hash, shift)
return _insert(x, shift)
}
for i := range n.leaf {
if key == n.leaf[i].k {
// replace existing entry
l := make([]leaf, 1, len(n.leaf))
l := make([]leaf, len(n.leaf))
l[0] = leaf{key, val}
l = append(l, n.leaf[:i]...)
l = append(l, n.leaf[i+1:]...)
copy(l[1:], n.leaf[:i])
copy(l[1+i:], n.leaf[i+1:])
added = false
return collision{hash, l}
}
@ -230,11 +263,12 @@ func (p pmap) stats() stats {
case *node:
s.count++
s.nodeCount++
s.emptySlots += bits.OnesCount32(^n.bitmap)
td += float64(bits.OnesCount32(n.bitmap))
for i := range n.child {
if n.child[i] != nil {
td += 1.0
visit(n.child[i], h+1)
} else {
s.emptySlots++
}
}
case collision:

View File

@ -12,6 +12,8 @@ func hash(k Key) uint32 {
return uint32(u + u>>32)
}
// FIXME: collisions can cause allocations during Get
func TestPmap(t *testing.T) {
p := New(hash)
const numElems = 100
@ -172,3 +174,18 @@ func benchmarkHmapSet(b *testing.B, numElems int) {
delete(h, k)
}
}
func TestBitmap(t *testing.T) {
n := node{bitmap: 0xff0a}
const x = -1
want := []int{x, 0, x, 1, x, x, x, x, 2, 3, 4, 5, 6, 7, 8, 9}
for i, j := range want {
if j == x {
continue
}
m := bitmask(uint32(i))
if got := n.index(m); got != j {
t.Errorf("got %v, want %v", got, j)
}
}
}