Skip to content

Commit

Permalink
Use 0 as empty metadata to allocate faster
Browse files Browse the repository at this point in the history
Current value has to be written for all metadata values when we're
instantiating a new map or growing.

This changes empty to be the zero byte, and tombstone to be byte 1, by
adding 2 (h2Offset) to all hashes.

Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com>
  • Loading branch information
colega committed Jul 4, 2024
1 parent 8ccae17 commit 8098dea
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 33 deletions.
2 changes: 1 addition & 1 deletion bits.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func metaMatchH2(m *metadata, h h2) bitset {
}

func metaMatchEmpty(m *metadata) bitset {
return hasZeroByte(castUint64(m) ^ hiBits)
return hasZeroByte(castUint64(m))
}

func nextMatch(b *bitset) uint32 {
Expand Down
27 changes: 15 additions & 12 deletions bits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,40 @@ import (
func TestMatchMetadata(t *testing.T) {
var meta metadata
for i := range meta {
meta[i] = uint8(i)
meta[i] = uint8(i) + h2Offset
}
t.Run("metaMatchH2", func(t *testing.T) {
for _, x := range meta {
mask := metaMatchH2(&meta, h2(x))
for i, m := range meta {
mask := metaMatchH2(&meta, h2(m))
assert.NotZero(t, mask)
assert.Equal(t, uint32(x), nextMatch(&mask))
assert.Equal(t, uint32(i), nextMatch(&mask))
}
})
t.Run("metaMatchEmpty", func(t *testing.T) {
mask := metaMatchEmpty(&meta)
assert.Equal(t, mask, bitset(0))

for i := range meta {
meta[i] = empty
mask = metaMatchEmpty(&meta)
assert.NotZero(t, mask)
assert.Equal(t, uint32(i), nextMatch(&mask))
meta[i] = uint8(i)
meta[i] = uint8(i) + h2Offset
}
})
t.Run("nextMatch", func(t *testing.T) {
const needle = uint8(42) + h2Offset

// test iterating multiple matches
meta = newEmptyMetadata()
meta = metadata{}
mask := metaMatchEmpty(&meta)
for i := range meta {
assert.Equal(t, uint32(i), nextMatch(&mask))
}
for i := 0; i < len(meta); i += 2 {
meta[i] = uint8(42)
meta[i] = needle
}
mask = metaMatchH2(&meta, h2(42))
mask = metaMatchH2(&meta, h2(needle))
for i := 0; i < len(meta); i += 2 {
assert.Equal(t, uint32(i), nextMatch(&mask))
}
Expand Down Expand Up @@ -90,10 +93,10 @@ func nextPow2(x uint32) uint32 {
}

func TestConstants(t *testing.T) {
assert.Equal(t, byte(0b1000_0000), empty)
assert.Equal(t, byte(0b1000_0000), reinterpretCast(empty))
assert.Equal(t, byte(0b1111_1110), tombstone)
assert.Equal(t, byte(0b1111_1110), reinterpretCast(tombstone))
assert.Equal(t, byte(0b0000_0000), empty)
assert.Equal(t, byte(0b0000_0000), reinterpretCast(empty))
assert.Equal(t, byte(0b0000_0001), tombstone)
assert.Equal(t, byte(0b0000_0001), reinterpretCast(tombstone))
}

func reinterpretCast(i uint8) byte {
Expand Down
29 changes: 9 additions & 20 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ type group[K comparable, V any] struct {
const (
h1Mask uint64 = 0xffff_ffff_ffff_ff80
h2Mask uint64 = 0x0000_0000_0000_007f
empty uint8 = 0b1000_0000
tombstone uint8 = 0b1111_1110
h2Offset = 2
empty uint8 = 0b0000_0000
tombstone uint8 = 0b0000_0001
)

// h1 is a 57 bit hash prefix
Expand All @@ -66,9 +67,6 @@ func NewMap[K comparable, V any](sz uint32) (m *Map[K, V]) {
hash: maphash.NewHasher[K](),
limit: groups * maxAvgGroupLoad,
}
for i := range m.ctrl {
m.ctrl[i] = newEmptyMetadata()
}
return
}

Expand Down Expand Up @@ -236,10 +234,8 @@ func (m *Map[K, V]) Iter(cb func(k K, v V) (stop bool)) {

// Clear removes all elements from the Map.
func (m *Map[K, V]) Clear() {
for i, c := range m.ctrl {
for j := range c {
m.ctrl[i][j] = empty
}
for i := range m.ctrl {
m.ctrl[i] = metadata{}
}
var k K
var v V
Expand Down Expand Up @@ -302,9 +298,6 @@ func (m *Map[K, V]) rehash(n uint32) {
groups, ctrl := m.groups, m.ctrl
m.groups = make([]group[K, V], n)
m.ctrl = make([]metadata, n)
for i := range m.ctrl {
m.ctrl[i] = newEmptyMetadata()
}
m.hash = maphash.NewSeed(m.hash)
m.limit = n * maxAvgGroupLoad
m.resident, m.dead = 0, 0
Expand Down Expand Up @@ -333,15 +326,11 @@ func numGroups(n uint32) (groups uint32) {
return
}

func newEmptyMetadata() (meta metadata) {
for i := range meta {
meta[i] = empty
}
return
}

// splitHash extracts the h1 and h2 components from a 64 bit hash.
// h1 is the upper 57 bits, h2 is the lower 7 bits plus two.
// By adding 2, it ensures that h2 is never uint8(0) or uint8(1).
func splitHash(h uint64) (h1, h2) {
return h1((h & h1Mask) >> 7), h2(h & h2Mask)
return h1((h & h1Mask) >> 7), h2(h&h2Mask) + h2Offset
}

func probeStart(hi h1, groups int) uint32 {
Expand Down

0 comments on commit 8098dea

Please sign in to comment.