Skip to content

Commit

Permalink
Use pointers to individual elements (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
outofforest authored Sep 11, 2024
1 parent d9d1767 commit e07f4fe
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 77 deletions.
88 changes: 59 additions & 29 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,67 @@ type key struct {
key [32]byte
}

func BenchmarkMaps(b *testing.B) {
b.StopTimer()
b.ResetTimer()
const (
keysNum = 10_000_000
loop1 = 1000
loop2 = 30
)

db := map[key]int{}
keys := make([]key, 0, 1000_000)
var (
keys []key
dbMap map[key]int
dbQuantum Snapshot[key, int]
)

func init() {
keys = make([]key, 0, keysNum)

for i := range cap(keys) {
for range cap(keys) {
var k key
_, _ = rand.Read(k.store[:])
_, _ = rand.Read(k.key[:])
keys = append(keys, k)
}

db[k] = i
dbMap = map[key]int{}
for i, k := range keys {
dbMap[k] = i
}

dbQuantum = New[key, int]()
for i, k := range keys {
dbQuantum.Set(k, i)
}

for i := len(keys) - 1; i > 0; i-- {
j := mathrand.Intn(i + 1)
keys[i], keys[j] = keys[j], keys[i]
}
}

func BenchmarkMaps(b *testing.B) {
b.StopTimer()
b.ResetTimer()

snapshot1 := map[key]int{}
snapshot2 := map[key]int{}

for range b.N {
rands := make([]int, 0, loop1*loop2)
for range cap(rands) {
rands = append(rands, mathrand.Intn(len(keys)))
}

var ri int

b.StartTimer()
for range 1000 {
for range 30 {
k := keys[mathrand.Intn(len(keys))]
for range loop1 {
for range loop2 {
k := keys[rands[ri]]
ri++
v2 := snapshot2[k]
v1 := snapshot1[k]
v := db[k]
v := dbMap[k]
snapshot2[k] = v + v1 + v2
}
for k, v := range snapshot2 {
Expand All @@ -49,7 +83,7 @@ func BenchmarkMaps(b *testing.B) {
clear(snapshot2)
}
for k, v := range snapshot1 {
db[k] = v
dbMap[k] = v
}
clear(snapshot1)
b.StopTimer()
Expand All @@ -60,26 +94,22 @@ func BenchmarkQuantum(b *testing.B) {
b.StopTimer()
b.ResetTimer()

db := New[key, int]()
keys := make([]key, 0, 1000_000)

for i := range cap(keys) {
var k key
_, _ = rand.Read(k.store[:])
_, _ = rand.Read(k.key[:])
keys = append(keys, k)
for range b.N {
rands := make([]int, 0, loop1*loop2)
for range cap(rands) {
rands = append(rands, mathrand.Intn(len(keys)))
}

db.Set(k, i)
}
var ri int

for range b.N {
b.StartTimer()
for range 1000 {
db = db.Next()
for range 30 {
k := keys[mathrand.Intn(len(keys))]
v, _ := db.Get(k)
db.Set(k, v)
for range loop1 {
dbQuantum = dbQuantum.Next()
for range loop2 {
k := keys[rands[ri]]
ri++
v, _ := dbQuantum.Get(k)
dbQuantum.Set(k, v)
}
}
b.StopTimer()
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22

require (
github.com/cespare/xxhash v1.1.0
github.com/outofforest/mass v0.2.1
github.com/outofforest/photon v0.5.0
github.com/stretchr/testify v1.9.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/outofforest/mass v0.2.1 h1:oIzOnoTJqN8eVXo5jxk1htOhW7bL7hy2JHrvnTsfvtU=
github.com/outofforest/mass v0.2.1/go.mod h1:rqr19KwYSKncmsmZCmMatTsg8pI+ElxerH9v1SGU1CQ=
github.com/outofforest/photon v0.5.0 h1:7Mq92+Dwj7TPOIZzbwOYBe05OLOP0d7GtRFvOGaU000=
github.com/outofforest/photon v0.5.0/go.mod h1:4qOhLdJ3jiXj7umpt57hCGs5T+p3LX9QdpkipX4YDy4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down
94 changes: 47 additions & 47 deletions quantum.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/cespare/xxhash"

"github.com/outofforest/mass"
"github.com/outofforest/photon"
)

Expand All @@ -27,23 +28,26 @@ const (

// New creates new quantum store.
func New[K comparable, V any]() Snapshot[K, V] {
return Snapshot[K, V]{
root: node[K, V]{
KVs: &[arraySize]kvPair[K, V]{},
},
s := Snapshot[K, V]{
rootNodeType: kvPointerType,
defaultValue: *new(V),
massNodes: mass.New[node[K, V]](1000),
massKVPairs: mass.New[kvPair[K, V]](1000),
}
s.root = s.massNodes.New()
return s
}

// Snapshot represents the state at particular point in time.
type Snapshot[K comparable, V any] struct {
version uint64
root node[K, V]
root *node[K, V]
rootNodeType pointerType
defaultValue V
hasher hasher[K]
hashMod uint64
massNodes *mass.Mass[node[K, V]]
massKVPairs *mass.Mass[kvPair[K, V]]
}

// Next transitions to the next snapshot of the state.
Expand Down Expand Up @@ -87,33 +91,30 @@ func (s *Snapshot[K, V]) Get(key K) (value V, exists bool) {
func (s *Snapshot[K, V]) Set(key K, value V) {
h := s.hasher.Hash(key)
nType := s.rootNodeType
n := &s.root
n := s.root

var parentNode *node[K, V]
var parentIndex uint64

for {
if n.Version < s.version {
n2 := node[K, V]{
Version: s.version,
Types: n.Types,
hasher: n.hasher,
}
n2 := s.massNodes.New()
n2.Version = s.version
n2.Types = n.Types
n2.hasher = n.hasher
if nType == kvPointerType {
kvs := *n.KVs
n2.KVs = &kvs
n2.KVs = n.KVs
} else {
pointers := *n.Pointers
n2.Pointers = &pointers
n2.Pointers = n.Pointers
}

switch {
case parentNode == nil:
s.root = n2
n = &s.root
n = s.root
default:
parentNode.Pointers[parentIndex] = n2
n = &parentNode.Pointers[parentIndex]
n = parentNode.Pointers[parentIndex]
}
}

Expand All @@ -128,31 +129,33 @@ func (s *Snapshot[K, V]) Set(key K, value V) {
case nodePointerType:
if n.Types[index] == freePointerType {
n.Types[index] = kvPointerType
n.Pointers[index] = node[K, V]{
Version: s.version,
KVs: &[arraySize]kvPair[K, V]{},
}
n.Pointers[index] = s.massNodes.New()
n.Version = s.version
}
parentIndex = index
parentNode = n
nType = n.Types[index]
n = &n.Pointers[index]
n = n.Pointers[index]
default:
if n.Types[index] == freePointerType {
n.Types[index] = kvPointerType
n.KVs[index] = kvPair[K, V]{
Hash: h,
Key: key,
Value: value,
}
kv := s.massKVPairs.New()
kv.Hash = h
kv.Key = key
kv.Value = value
n.KVs[index] = kv
return
}

kv := n.KVs[index]
var conflict bool
if kv.Hash == h {
if kv.Key == key {
n.KVs[index].Value = value
kv2 := s.massKVPairs.New()
kv2.Hash = kv.Hash
kv2.Key = kv.Key
kv2.Value = value
n.KVs[index] = kv2
return
}

Expand All @@ -163,22 +166,18 @@ func (s *Snapshot[K, V]) Set(key K, value V) {

// conflict or split needed

n2 := node[K, V]{
Version: s.version,
Pointers: &[arraySize]node[K, V]{},
hasher: n.hasher,
}
n2 := s.massNodes.New()
n2.Version = s.version
n2.hasher = n.hasher

for i := range uint64(arraySize) {
if n.Types[i] == freePointerType {
continue
}

n2.Types[i] = kvPointerType
n2.Pointers[i] = node[K, V]{
Version: s.version,
KVs: &[arraySize]kvPair[K, V]{},
}
n2.Pointers[i] = s.massNodes.New()
n2.Pointers[i].Version = s.version

kv := n.KVs[i]
var hash uint64
Expand All @@ -192,25 +191,26 @@ func (s *Snapshot[K, V]) Set(key K, value V) {

index := hash & mask
n2.Pointers[i].Types[index] = kvPointerType
n2.Pointers[i].KVs[index] = kvPair[K, V]{
Hash: hash >> bitsPerHop,
Key: kv.Key,
Value: kv.Value,
}

kv2 := s.massKVPairs.New()
kv2.Hash = hash >> bitsPerHop
kv2.Key = kv.Key
kv2.Value = kv.Value
n2.Pointers[i].KVs[index] = kv2
}

if parentNode == nil {
s.rootNodeType = nodePointerType
s.root = n2
parentNode = &s.root
parentNode = s.root
} else {
parentNode.Types[parentIndex] = nodePointerType
parentNode.Pointers[parentIndex] = n2
parentNode = &parentNode.Pointers[parentIndex]
parentNode = parentNode.Pointers[parentIndex]
}

parentIndex = index
n = &n2.Pointers[index]
n = n2.Pointers[index]
}
}
}
Expand All @@ -226,8 +226,8 @@ type node[K comparable, V any] struct {
hasher hasher[K]

Types [arraySize]pointerType
KVs *[arraySize]kvPair[K, V]
Pointers *[arraySize]node[K, V]
KVs [arraySize]*kvPair[K, V]
Pointers [arraySize]*node[K, V]
}

func newHasher[K comparable](mod uint64) hasher[K] {
Expand Down
2 changes: 1 addition & 1 deletion quantum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func TestFindCollisions(t *testing.T) {
func collect(s Snapshot[int, int]) []int {
values := []int{}
typeStack := []pointerType{s.rootNodeType}
nodeStack := []node[int, int]{s.root}
nodeStack := []*node[int, int]{s.root}

for {
if len(nodeStack) == 0 {
Expand Down

0 comments on commit e07f4fe

Please sign in to comment.