From e07f4fe52d249cb4ccddd91ea35258c0db66c32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Ma=C5=82ota-W=C3=B3jcik?= <59281144+outofforest@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:57:34 +0200 Subject: [PATCH] Use pointers to individual elements (#7) --- benchmark_test.go | 88 +++++++++++++++++++++++++++++--------------- go.mod | 1 + go.sum | 2 + quantum.go | 94 +++++++++++++++++++++++------------------------ quantum_test.go | 2 +- 5 files changed, 110 insertions(+), 77 deletions(-) diff --git a/benchmark_test.go b/benchmark_test.go index 8083d9d..deab1cd 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -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 { @@ -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() @@ -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() diff --git a/go.mod b/go.mod index f7809b3..9fa50dd 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 6f3cd81..79e058f 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/quantum.go b/quantum.go index b72914d..a8fbd21 100644 --- a/quantum.go +++ b/quantum.go @@ -5,6 +5,7 @@ import ( "github.com/cespare/xxhash" + "github.com/outofforest/mass" "github.com/outofforest/photon" ) @@ -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. @@ -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] } } @@ -128,23 +129,21 @@ 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 } @@ -152,7 +151,11 @@ func (s *Snapshot[K, V]) Set(key K, value V) { 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 } @@ -163,11 +166,9 @@ 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 { @@ -175,10 +176,8 @@ func (s *Snapshot[K, V]) Set(key K, value V) { } 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 @@ -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] } } } @@ -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] { diff --git a/quantum_test.go b/quantum_test.go index ae88341..6f26910 100644 --- a/quantum_test.go +++ b/quantum_test.go @@ -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 {