From ed97cd7544f861500d38dca76552c8917ef23ef8 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: Thu, 12 Dec 2024 09:52:55 +0100 Subject: [PATCH] Move code around and drop not needed pieces (#297) --- alloc/state.go | 2 +- db.go | 6 -- space/address.go | 25 ++++++++ space/data.go | 27 +++------ space/space.go | 145 ++++++++++++++++++++++++-------------------- space/space_test.go | 10 +-- types/types.go | 51 +++------------- 7 files changed, 127 insertions(+), 139 deletions(-) create mode 100644 space/address.go diff --git a/alloc/state.go b/alloc/state.go index bb2b269..84165b6 100644 --- a/alloc/state.go +++ b/alloc/state.go @@ -102,7 +102,7 @@ func (s *State) VolatileSize() uint64 { // Node returns node bytes. func (s *State) Node(nodeAddress types.VolatileAddress) unsafe.Pointer { - return unsafe.Add(s.dataP, nodeAddress*types.NodeLength) + return unsafe.Add(s.dataP, nodeAddress.Naked()*types.NodeLength) } // Bytes returns byte slice of a node. diff --git a/db.go b/db.go index e140be6..163b999 100644 --- a/db.go +++ b/db.go @@ -253,16 +253,13 @@ func (db *DB) deleteSnapshot( }, ) - //nolint:nestif if nextSnapshotInfo.DeallocationRoot.VolatileAddress != types.FreeAddress { - var err error for nextDeallocSnapshot := range space.IteratorAndDeallocator( nextSnapshotInfo.DeallocationRoot, db.config.State, deallocationNodeAssistant, volatileDeallocator, persistentDeallocator, - &err, ) { if nextDeallocSnapshot.Key.SnapshotID > snapshotInfo.PreviousSnapshotID && nextDeallocSnapshot.Key.SnapshotID <= snapshotID { @@ -287,9 +284,6 @@ func (db *DB) deleteSnapshot( return err } } - if err != nil { - return err - } } nextSnapshotInfo.DeallocationRoot = snapshotInfo.DeallocationRoot diff --git a/space/address.go b/space/address.go new file mode 100644 index 0000000..397f536 --- /dev/null +++ b/space/address.go @@ -0,0 +1,25 @@ +package space + +import ( + "github.com/outofforest/quantum/types" +) + +const ( + // flagPointerNode says that this is pointer node. + flagPointerNode = types.FlagNaked + 1 + + // flagHashMod says that key hash must be recalculated. + flagHashMod = flagPointerNode << 1 +) + +func isFree(address types.VolatileAddress) bool { + return address == types.FreeAddress +} + +func isPointer(address types.VolatileAddress) bool { + return address.IsSet(flagPointerNode) +} + +func isData(address types.VolatileAddress) bool { + return !isFree(address) && !isPointer(address) +} diff --git a/space/data.go b/space/data.go index 8ce7f27..613c3a4 100644 --- a/space/data.go +++ b/space/data.go @@ -8,9 +8,15 @@ import ( "github.com/outofforest/quantum/types" ) +// DataItem stores single key-value pair. +type DataItem[K, V comparable] struct { + Key K + Value V +} + // NewDataNodeAssistant creates new space data node assistant. func NewDataNodeAssistant[K, V comparable]() (*DataNodeAssistant[K, V], error) { - itemSize := uint64(unsafe.Sizeof(types.DataItem[K, V]{})+types.UInt64Length-1) / + itemSize := uint64(unsafe.Sizeof(DataItem[K, V]{})+types.UInt64Length-1) / types.UInt64Length * types.UInt64Length numOfItems := types.NodeLength / (itemSize + types.UInt64Length) // Uint64Length is for key hash. @@ -45,8 +51,8 @@ func (na *DataNodeAssistant[K, V]) ItemOffset(index uint64) uint64 { } // Item maps the memory address given by the node address and offset to an item. -func (na *DataNodeAssistant[K, V]) Item(n unsafe.Pointer, offset uint64) *types.DataItem[K, V] { - return (*types.DataItem[K, V])(unsafe.Add(n, offset)) +func (na *DataNodeAssistant[K, V]) Item(n unsafe.Pointer, offset uint64) *DataItem[K, V] { + return (*DataItem[K, V])(unsafe.Add(n, offset)) } // KeyHashes returns slice of key hashes stored in the node. @@ -55,18 +61,3 @@ func (na *DataNodeAssistant[K, V]) KeyHashes(n unsafe.Pointer) []types.KeyHash { // alignment to compare them. return unsafe.Slice((*types.KeyHash)(n), na.numOfItems) } - -// Iterator iterates over items. -func (na *DataNodeAssistant[K, V]) Iterator(n unsafe.Pointer) func(func(uint64, *types.DataItem[K, V]) bool) { - return func(yield func(uint64, *types.DataItem[K, V]) bool) { - keyHashP := n - itemP := unsafe.Add(n, na.itemOffset) - for i := range na.numOfItems { - if *(*types.KeyHash)(keyHashP) != 0 && !yield(i, (*types.DataItem[K, V])(itemP)) { - return - } - keyHashP = unsafe.Add(keyHashP, types.UInt64Length) - itemP = unsafe.Add(itemP, na.itemSize) - } - } -} diff --git a/space/space.go b/space/space.go index 4e9bc8e..5526d23 100644 --- a/space/space.go +++ b/space/space.go @@ -99,17 +99,17 @@ func (s *Space[K, V]) Query(key K, hashBuff []byte, hashMatches []uint64) (V, bo // Stats returns space-related statistics. func (s *Space[K, V]) Stats() (uint64, uint64, uint64, float64) { - switch s.config.SpaceRoot.Pointer.VolatileAddress.State() { - case types.StateFree: + switch { + case isFree(s.config.SpaceRoot.Pointer.VolatileAddress): return 0, 0, 0, 0 - case types.StateData: + case !isPointer(s.config.SpaceRoot.Pointer.VolatileAddress): return 1, 0, 1, 0 } - stack := []types.VolatileAddress{s.config.SpaceRoot.Pointer.VolatileAddress.Naked()} + stack := []types.VolatileAddress{s.config.SpaceRoot.Pointer.VolatileAddress} levels := map[types.VolatileAddress]uint64{ - s.config.SpaceRoot.Pointer.VolatileAddress.Naked(): 1, + s.config.SpaceRoot.Pointer.VolatileAddress: 1, } var maxLevel, pointerNodes, dataNodes, dataItems uint64 @@ -123,23 +123,24 @@ func (s *Space[K, V]) Stats() (uint64, uint64, uint64, float64) { pointerNodes++ stack = stack[:len(stack)-1] - pointerNode := ProjectPointerNode(s.config.State.Node(n.Naked())) + pointerNode := ProjectPointerNode(s.config.State.Node(n)) for pi := range pointerNode.Pointers { volatileAddress := types.Load(&pointerNode.Pointers[pi].VolatileAddress) - switch volatileAddress.State() { - case types.StateFree: - case types.StateData: + switch { + case isFree(volatileAddress): + case isPointer(volatileAddress): + stack = append(stack, volatileAddress) + levels[volatileAddress] = level + default: dataNodes++ if level > maxLevel { maxLevel = level } - //nolint:gofmt,revive // looks like a bug in linter - for _, _ = range s.config.DataNodeAssistant.Iterator(s.config.State.Node(volatileAddress)) { - dataItems++ + for _, kh := range s.config.DataNodeAssistant.KeyHashes(s.config.State.Node(volatileAddress)) { + if kh != 0 { + dataItems++ + } } - case types.StatePointer: - stack = append(stack, volatileAddress.Naked()) - levels[volatileAddress.Naked()] = level } } } @@ -155,19 +156,19 @@ func (s *Space[K, V]) query( volatileAddress := s.config.SpaceRoot.Pointer.VolatileAddress var level uint8 - for volatileAddress.IsSet(types.FlagPointerNode) { - if volatileAddress.IsSet(types.FlagHashMod) { + for isPointer(volatileAddress) { + if volatileAddress.IsSet(flagHashMod) { keyHash = hashKeyFunc(&key, hashBuff, level) } - pointerNode := ProjectPointerNode(s.config.State.Node(volatileAddress.Naked())) + pointerNode := ProjectPointerNode(s.config.State.Node(volatileAddress)) index, _ := reducePointerSlot(pointerNode, PointerIndex(keyHash, level)) level++ volatileAddress = pointerNode.Pointers[index].VolatileAddress } - if volatileAddress == types.FreeAddress { + if isFree(volatileAddress) { return s.defaultValue, false } @@ -286,8 +287,8 @@ func (s *Space[K, V]) find( return } - if types.Load(&v.storeRequest.Store[v.storeRequest.PointersToStore-1].Pointer.VolatileAddress). - State() != types.StateData { + volatileAddress := types.Load(&v.storeRequest.Store[v.storeRequest.PointersToStore-1].Pointer.VolatileAddress) + if !isData(volatileAddress) { v.keyHashP = nil v.itemP = nil v.exists = false @@ -310,7 +311,7 @@ func (s *Space[K, V]) set( volatileAddress := types.Load(&v.storeRequest.Store[v.storeRequest.PointersToStore-1].Pointer.VolatileAddress) - if volatileAddress == types.FreeAddress { + if isFree(volatileAddress) { dataNodeVolatileAddress, err := allocator.Allocate() if err != nil { return err @@ -371,11 +372,11 @@ func (s *Space[K, V]) splitToIndex(parentNodeAddress types.VolatileAddress, inde } mask := uint64(1) << trailingZeros - parentNode := ProjectPointerNode(s.config.State.Node(parentNodeAddress.Naked())) + parentNode := ProjectPointerNode(s.config.State.Node(parentNodeAddress)) for mask > 1 { mask >>= 1 newIndex := index | mask - if types.Load(&parentNode.Pointers[newIndex].VolatileAddress) == types.FreeAddress { + if isFree(types.Load(&parentNode.Pointers[newIndex].VolatileAddress)) { index = newIndex break } @@ -402,23 +403,26 @@ func (s *Space[K, V]) splitDataNodeWithoutConflict( } s.config.State.Clear(newNodeVolatileAddress) - parentNode := ProjectPointerNode(s.config.State.Node(types.Load(&parentNodePointer.VolatileAddress).Naked())) + parentNode := ProjectPointerNode(s.config.State.Node(types.Load(&parentNodePointer.VolatileAddress))) existingNodePointer := &parentNode.Pointers[index] existingDataNode := s.config.State.Node(types.Load(&existingNodePointer.VolatileAddress)) newDataNode := s.config.State.Node(newNodeVolatileAddress) keyHashes := s.config.DataNodeAssistant.KeyHashes(existingDataNode) newKeyHashes := s.config.DataNodeAssistant.KeyHashes(newDataNode) - for i, item := range s.config.DataNodeAssistant.Iterator(existingDataNode) { - itemIndex := PointerIndex(keyHashes[i], level-1) + for i, kh := range keyHashes { + if kh == 0 { + continue + } + + itemIndex := PointerIndex(kh, level-1) if itemIndex&mask != newIndex { continue } - // FIXME (wojciech): Items might be inserted on the first free slot in the new node. - newDataNodeItem := s.config.DataNodeAssistant.Item(newDataNode, s.config.DataNodeAssistant.ItemOffset(i)) - *newDataNodeItem = *item - newKeyHashes[i] = keyHashes[i] + offset := s.config.DataNodeAssistant.ItemOffset(uint64(i)) + *s.config.DataNodeAssistant.Item(newDataNode, offset) = *s.config.DataNodeAssistant.Item(existingDataNode, offset) + newKeyHashes[i] = kh keyHashes[i] = 0 } @@ -468,25 +472,30 @@ func (s *Space[K, V]) splitDataNodeWithConflict( } s.config.State.Clear(newNodeVolatileAddress) - parentNode := ProjectPointerNode(s.config.State.Node(types.Load(&parentNodePointer.VolatileAddress).Naked())) + parentNode := ProjectPointerNode(s.config.State.Node(types.Load(&parentNodePointer.VolatileAddress))) existingNodePointer := &parentNode.Pointers[index] existingDataNode := s.config.State.Node(types.Load(&existingNodePointer.VolatileAddress)) newDataNode := s.config.State.Node(newNodeVolatileAddress) keyHashes := s.config.DataNodeAssistant.KeyHashes(existingDataNode) newKeyHashes := s.config.DataNodeAssistant.KeyHashes(newDataNode) - for i, item := range s.config.DataNodeAssistant.Iterator(existingDataNode) { - keyHash := hashKeyFunc(&item.Key, hashBuff, level-1) - itemIndex := PointerIndex(keyHash, level-1) + for i, kh := range keyHashes { + if kh == 0 { + continue + } + + offset := s.config.DataNodeAssistant.ItemOffset(uint64(i)) + item := s.config.DataNodeAssistant.Item(existingDataNode, offset) + + kh = hashKeyFunc(&item.Key, hashBuff, level-1) + itemIndex := PointerIndex(kh, level-1) if itemIndex&mask != newIndex { - keyHashes[i] = keyHash + keyHashes[i] = kh continue } - // FIXME (wojciech): Items might be inserted on the first free slot in the new node. - newDataNodeItem := s.config.DataNodeAssistant.Item(newDataNode, s.config.DataNodeAssistant.ItemOffset(i)) - *newDataNodeItem = *item - newKeyHashes[i] = keyHash + *s.config.DataNodeAssistant.Item(newDataNode, offset) = *item + newKeyHashes[i] = kh keyHashes[i] = 0 } @@ -538,9 +547,9 @@ func (s *Space[K, V]) addPointerNode( pointerNode.Pointers[0].PersistentAddress = dataPointer.PersistentAddress types.Store(&pointerNode.Pointers[0].VolatileAddress, dataPointer.VolatileAddress) - pointerNodeVolatileAddress = pointerNodeVolatileAddress.Set(types.FlagPointerNode) + pointerNodeVolatileAddress = pointerNodeVolatileAddress.Set(flagPointerNode) if conflict { - pointerNodeVolatileAddress = pointerNodeVolatileAddress.Set(types.FlagHashMod) + pointerNodeVolatileAddress = pointerNodeVolatileAddress.Set(flagHashMod) } types.Store(&pointerNodeRoot.Pointer.VolatileAddress, pointerNodeVolatileAddress) @@ -559,7 +568,7 @@ func (s *Space[K, V]) walkPointers( hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) { if v.nextDataNode != nil { - if types.Load(v.nextDataNode) == types.FreeAddress { + if isFree(types.Load(v.nextDataNode)) { return } @@ -584,14 +593,14 @@ func (s *Space[K, V]) walkOnePointer( hashKeyFunc func(key *K, buff []byte, level uint8) types.KeyHash, ) bool { volatileAddress := types.Load(&v.storeRequest.Store[v.storeRequest.PointersToStore-1].Pointer.VolatileAddress) - if !volatileAddress.IsSet(types.FlagPointerNode) { + if !isPointer(volatileAddress) { return false } - if volatileAddress.IsSet(types.FlagHashMod) { + if volatileAddress.IsSet(flagHashMod) { v.keyHash = hashKeyFunc(&v.item.Key, hashBuff, v.level) } - pointerNode := ProjectPointerNode(s.config.State.Node(volatileAddress.Naked())) + pointerNode := ProjectPointerNode(s.config.State.Node(volatileAddress)) index, nextIndex := reducePointerSlot(pointerNode, PointerIndex(v.keyHash, v.level)) v.level++ @@ -660,8 +669,7 @@ func (s *Space[K, V]) detectUpdate(v *Entry[K, V]) { return } - if types.Load(&v.storeRequest.Store[v.storeRequest.PointersToStore-1].Pointer. - VolatileAddress).IsSet(types.FlagPointerNode) || + if isPointer(types.Load(&v.storeRequest.Store[v.storeRequest.PointersToStore-1].Pointer.VolatileAddress)) || *s.config.DeletionCounter != v.deletionCounter || (*v.keyHashP != 0 && (*v.keyHashP != v.keyHash || v.itemP.Key != v.item.Key)) { v.keyHashP = nil @@ -674,10 +682,10 @@ type Entry[K, V comparable] struct { space *Space[K, V] storeRequest pipeline.StoreRequest - itemP *types.DataItem[K, V] + itemP *DataItem[K, V] keyHashP *types.KeyHash keyHash types.KeyHash - item types.DataItem[K, V] + item DataItem[K, V] parentIndex uint64 dataItemIndex uint64 nextDataNode *types.VolatileAddress @@ -735,10 +743,9 @@ func IteratorAndDeallocator[K, V comparable]( dataNodeAssistant *DataNodeAssistant[K, V], volatileDeallocator *alloc.Deallocator[types.VolatileAddress], persistentDeallocator *alloc.Deallocator[types.PersistentAddress], - retErr *error, -) func(func(item *types.DataItem[K, V]) bool) { - return func(yield func(item *types.DataItem[K, V]) bool) { - if spaceRoot.VolatileAddress == types.FreeAddress { +) func(func(item *DataItem[K, V]) bool) { + return func(yield func(item *DataItem[K, V]) bool) { + if isFree(spaceRoot.VolatileAddress) { return } @@ -757,23 +764,26 @@ func IteratorAndDeallocator[K, V comparable]( pointer := stack[stackCount] // It is safe to do deallocations here because nodes are not reallocated until commit is finalized. - volatileDeallocator.Deallocate(pointer.VolatileAddress) + volatileDeallocator.Deallocate(pointer.VolatileAddress.Naked()) persistentDeallocator.Deallocate(pointer.PersistentAddress) - switch pointer.VolatileAddress.State() { - case types.StatePointer: + switch { + case isPointer(pointer.VolatileAddress): pointerNode := ProjectPointerNode(state.Node(pointer.VolatileAddress)) for pi := range pointerNode.Pointers { - if pointerNode.Pointers[pi].VolatileAddress == types.FreeAddress { + if isFree(pointerNode.Pointers[pi].VolatileAddress) { continue } stack[stackCount] = pointerNode.Pointers[pi] stackCount++ } - case types.StateData: - for _, item := range dataNodeAssistant.Iterator(state.Node(pointer.VolatileAddress)) { - if !yield(item) { + case !isFree(pointer.VolatileAddress): + n := state.Node(pointer.VolatileAddress) + keyHashes := dataNodeAssistant.KeyHashes(n) + + for i, kh := range keyHashes { + if kh != 0 && !yield(dataNodeAssistant.Item(n, dataNodeAssistant.ItemOffset(uint64(i)))) { return } } @@ -868,7 +878,7 @@ var pointerHops = [NumOfPointers][]uint64{ } func reducePointerSlot(pointerNode *PointerNode, index uint64) (uint64, uint64) { - if types.Load(&pointerNode.Pointers[index].VolatileAddress) != types.FreeAddress { + if !isFree(types.Load(&pointerNode.Pointers[index].VolatileAddress)) { return index, index } @@ -884,8 +894,9 @@ func reducePointerSlot(pointerNode *PointerNode, index uint64) (uint64, uint64) hopIndex := (hopEnd-hopStart)/2 + hopStart newIndex := hops[hopIndex] - switch types.Load(&pointerNode.Pointers[newIndex].VolatileAddress).State() { - case types.StateFree: + volatileAddress := types.Load(&pointerNode.Pointers[newIndex].VolatileAddress) + switch { + case isFree(volatileAddress): if !dataFound { index = newIndex if hopIndex == 0 { @@ -895,7 +906,9 @@ func reducePointerSlot(pointerNode *PointerNode, index uint64) (uint64, uint64) } } hopStart = hopIndex + 1 - case types.StateData: + case isPointer(volatileAddress): + hopEnd = hopIndex + default: index = newIndex if hopIndex == 0 { nextIndex = originalIndex @@ -904,8 +917,6 @@ func reducePointerSlot(pointerNode *PointerNode, index uint64) (uint64, uint64) } hopEnd = hopIndex dataFound = true - case types.StatePointer: - hopEnd = hopIndex } } diff --git a/space/space_test.go b/space/space_test.go index cd2cb24..733856a 100644 --- a/space/space_test.go +++ b/space/space_test.go @@ -104,7 +104,7 @@ func TestPointerSlotReductionUsingPointerNodes(t *testing.T) { for hi := len(hops) - 1; hi >= 0; hi-- { pointerNode.Pointers[hops[hi]].VolatileAddress = 1 // To mark it as data node. if hi < len(hops)-1 { - pointerNode.Pointers[hops[hi+1]].VolatileAddress = types.FlagPointerNode // To mark it as pointer node. + pointerNode.Pointers[hops[hi+1]].VolatileAddress = flagPointerNode // To mark it as pointer node. } index, nextIndex := reducePointerSlot(pointerNode, i) requireT.Equal(hops[hi], index) @@ -117,13 +117,13 @@ func TestPointerSlotReductionUsingPointerNodes(t *testing.T) { pointerNode.Pointers[i].VolatileAddress = 1 // To mark it as data node. if len(hops) > 0 { - pointerNode.Pointers[hops[0]].VolatileAddress = types.FlagPointerNode // To mark it as pointer node. + pointerNode.Pointers[hops[0]].VolatileAddress = flagPointerNode // To mark it as pointer node. } index, nextIndex = reducePointerSlot(pointerNode, i) requireT.Equal(i, index) requireT.Equal(i, nextIndex) - pointerNode.Pointers[i].VolatileAddress = types.FlagPointerNode // To mark it as pointer node. + pointerNode.Pointers[i].VolatileAddress = flagPointerNode // To mark it as pointer node. index, nextIndex = reducePointerSlot(pointerNode, i) requireT.Equal(i, index) requireT.Equal(i, nextIndex) @@ -1061,9 +1061,9 @@ func TestSwitchingFromMutableToImmutablePath(t *testing.T) { // Now let's add a pointer node at the position of key hash 64. requireT.NoError(s.AddPointerNode(v64, false)) - requireT.True(pointerNode.Pointers[0].VolatileAddress.IsSet(types.FlagPointerNode)) + requireT.True(pointerNode.Pointers[0].VolatileAddress.IsSet(flagPointerNode)) requireT.True(v16Read.storeRequest.Store[v16Read.storeRequest.PointersToStore-1].Pointer.VolatileAddress. - IsSet(types.FlagPointerNode)) + IsSet(flagPointerNode)) // We are now in situation where key hash 16 is no longer in the place pointed to by v16. // When walking the tree now, it should not follow the current pointer node, but go back and switch diff --git a/types/types.go b/types/types.go index 33c92f5..403a6a1 100644 --- a/types/types.go +++ b/types/types.go @@ -1,6 +1,9 @@ package types -import "sync/atomic" +import ( + "math" + "sync/atomic" +) const ( // UInt64Length is the number of bytes taken by uint64. @@ -40,18 +43,6 @@ type ( VolatileAddress uint64 ) -// State returns node state. -func (na VolatileAddress) State() State { - switch { - case na.IsSet(FlagPointerNode): - return StatePointer - case na == FreeAddress: - return StateFree - default: - return StateData - } -} - // IsSet checks if flag is set. func (na VolatileAddress) IsSet(flag VolatileAddress) bool { return na&flag != FreeAddress @@ -64,7 +55,7 @@ func (na VolatileAddress) Set(flag VolatileAddress) VolatileAddress { // Naked returns address without flags. func (na VolatileAddress) Naked() VolatileAddress { - return na & flagNaked + return na & FlagNaked } // Load loads node address atomically. @@ -78,31 +69,13 @@ func Store(address *VolatileAddress, value VolatileAddress) { } const ( + numOfFlags = 2 + // FreeAddress means address is not assigned. FreeAddress VolatileAddress = 0 - // flagNaked is used to retrieve address without flags. - flagNaked = FlagPointerNode - 1 - - // FlagPointerNode says that this is pointer node. - FlagPointerNode VolatileAddress = 1 << 62 - - // FlagHashMod says that key hash must be recalculated. - FlagHashMod VolatileAddress = 1 << 63 -) - -// State enumerates possible slot states. -type State byte - -const ( - // StateFree means slot is free. - StateFree State = iota - - // StateData means slot contains data. - StateData - - // StatePointer means slot contains pointer. - StatePointer + // FlagNaked is used to retrieve address without flags. + FlagNaked VolatileAddress = math.MaxUint64 >> numOfFlags ) // Pointer is the pointer to another block. @@ -126,12 +99,6 @@ type Root struct { Hash Hash } -// DataItem stores single key-value pair. -type DataItem[K, V comparable] struct { - Key K - Value V -} - // SnapshotInfo stores information required to retrieve snapshot. type SnapshotInfo struct { PreviousSnapshotID SnapshotID