From 1fa0272800f4ab6fbb1686e7354572eee0e62c40 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Tue, 4 Mar 2025 09:49:32 -0600 Subject: [PATCH 1/2] Add util functions to move elements in slices This commit adds internal functions to move elements. The new functions can be used to simplify slab operations, such as Merge(), Split(), LendToRight(), and BorrowFromRight(). --- slice_utils.go | 78 +++++++++++++ slice_utils_test.go | 268 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 slice_utils.go create mode 100644 slice_utils_test.go diff --git a/slice_utils.go b/slice_utils.go new file mode 100644 index 0000000..d8ee657 --- /dev/null +++ b/slice_utils.go @@ -0,0 +1,78 @@ +/* + * Atree - Scalable Arrays and Ordered Maps + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package atree + +import "slices" + +// split splits s into two slices with left slice of leftCount length and right slice of remaining elements. +// Returned left is resliced s, and returned right is new slice. +func split[S ~[]E, E any](s S, leftCount int) (left S, right S) { + _ = s[leftCount:] // bounds check + + right = slices.Clone(s[leftCount:]) + left = slices.Delete(s, leftCount, len(s)) + return left, right +} + +// merge returnes concatenated left and right slices. +// If left slice has sufficient capacity, returned slice is resliced to append elements from right. +// Right slice is cleared. +func merge[S ~[]E, E any](left, right S) S { + left = append(left, right...) + clear(right) + return left +} + +// lendToRight moves elements from tail of left slice to head of right slice. +func lendToRight[S ~[]E, E any](left, right S, count int) (S, S) { + leftIndex := len(left) - count + + _ = left[leftIndex:] // bounds check + + // Prepend elements from the tail of left slice to the head of right slice. + right = slices.Insert( + right, + 0, + left[leftIndex:]..., + ) + + // Remove moved elements from left + left = slices.Delete(left, leftIndex, len(left)) + + return left, right +} + +// borrowFromRight moves elements from head of right slice to tail of left slice. +func borrowFromRight[S ~[]E, E any](left, right S, count int) (S, S) { + _ = right[:count] // bounds check + + // Append moved elements to left + left = append(left, right[:count]...) + + // Move remaining elements in the right slice to the front + right = slices.Insert( + right[:0], + 0, + right[count:]...) + + // Clear moved elements to prevent memory leak + clear(right[len(right):cap(right)]) + + return left, right +} diff --git a/slice_utils_test.go b/slice_utils_test.go new file mode 100644 index 0000000..b13bce5 --- /dev/null +++ b/slice_utils_test.go @@ -0,0 +1,268 @@ +/* + * Atree - Scalable Arrays and Ordered Maps + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package atree + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSliceSplit(t *testing.T) { + t.Parallel() + + t.Run("count is out of range", func(t *testing.T) { + var s []int + assert.Panics(t, func() { _, _ = split(s, len(s)+1) }) + + s = []int{1} + assert.Panics(t, func() { _, _ = split(s, len(s)+1) }) + }) + + t.Run("empty slice", func(t *testing.T) { + var s []int + left, right := split(s, 0) + require.Equal(t, 0, len(left)) + require.Equal(t, 0, len(right)) + }) + + t.Run("non-empty slice", func(t *testing.T) { + + // split []int{1, 2, 3, 4, 5} at 0 + { + s := []int{1, 2, 3, 4, 5} + left, right := split(s, 0) + + require.Equal(t, 0, len(left)) + require.Equal(t, []int{0, 0, 0, 0, 0}, left[:cap(left)]) + + require.Equal(t, 5, len(right)) + require.Equal(t, []int{1, 2, 3, 4, 5}, right) + } + + // split []int{1, 2, 3, 4, 5} at 3 + { + s := []int{1, 2, 3, 4, 5} + p := &s[0] + left, right := split(s, 3) + + require.Equal(t, 3, len(left)) + require.Equal(t, []int{1, 2, 3}, left) + require.Equal(t, []int{1, 2, 3, 0, 0}, left[:cap(left)]) + require.Equal(t, p, &left[0]) + + require.Equal(t, 2, len(right)) + require.Equal(t, []int{4, 5}, right) + } + + // split []int{1, 2, 3, 4, 5} at len(s) + { + s := []int{1, 2, 3, 4, 5} + p := &s[0] + left, right := split(s, 5) + + require.Equal(t, 5, len(left)) + require.Equal(t, []int{1, 2, 3, 4, 5}, left) + require.Equal(t, p, &left[0]) + + require.Equal(t, 0, len(right)) + } + }) +} + +func TestSliceMerge(t *testing.T) { + t.Parallel() + + t.Run("empty slices", func(t *testing.T) { + s1 := []int{} + s2 := []int{} + s := merge(s1, s2) + require.Equal(t, 0, len(s)) + }) + + t.Run("empty left slice", func(t *testing.T) { + s1 := []int{} + s2 := []int{1, 2, 3} + s := merge(s1, s2) + require.Equal(t, 3, len(s)) + require.Equal(t, []int{1, 2, 3}, s) + }) + + t.Run("empty right slice", func(t *testing.T) { + s1 := []int{1, 2, 3} + p := &s1[0] + s2 := []int{} + s := merge(s1, s2) + require.Equal(t, 3, len(s)) + require.Equal(t, []int{1, 2, 3}, s) + require.Equal(t, p, &s[0]) + }) + + t.Run("non-empty slices", func(t *testing.T) { + s1 := make([]int, 3, 5) + s1[0] = 1 + s1[1] = 2 + s1[2] = 3 + p := &s1[0] + s2 := []int{4, 5} + s := merge(s1, s2) + require.Equal(t, 5, len(s)) + require.Equal(t, []int{1, 2, 3, 4, 5}, s) + require.Equal(t, p, &s[0]) + }) +} + +func TestSliceMoveFromLeftToRight(t *testing.T) { + t.Parallel() + + t.Run("count is out of range", func(t *testing.T) { + assert.Panics(t, func() { _, _ = lendToRight([]int{}, []int{0, 1, 2}, 1) }) + + assert.Panics(t, func() { _, _ = lendToRight([]int{0}, []int{0, 1, 2}, 2) }) + }) + + t.Run("move 0 element", func(t *testing.T) { + // left is empty slice + { + s1 := []int{} + s2 := []int{3, 4} + p2 := &s2[0] + s1b, s2b := lendToRight(s1, s2, 0) + require.Equal(t, []int{}, s1b) + require.Equal(t, []int{3, 4}, s2b) + require.Equal(t, p2, &s2b[0]) + } + + // left is non-empty slice + { + s1 := []int{1, 2} + p1 := &s1[0] + s2 := []int{3, 4} + p2 := &s2[0] + s1b, s2b := lendToRight(s1, s2, 0) + require.Equal(t, []int{1, 2}, s1b) + require.Equal(t, p1, &s1b[0]) + require.Equal(t, []int{3, 4}, s2b) + require.Equal(t, p2, &s2b[0]) + } + }) + + t.Run("move all elements", func(t *testing.T) { + // right is empty slice + { + s1 := []int{1, 2, 3} + oldlen := len(s1) + s2 := []int{} + s1b, s2b := lendToRight(s1, s2, len(s1)) + require.Equal(t, []int{}, s1b) + require.Equal(t, []int{0, 0, 0}, s1b[:oldlen]) + require.Equal(t, []int{1, 2, 3}, s2b) + } + + // right is non-empty slice + { + s1 := []int{1, 2, 3} + oldlen := len(s1) + s2 := []int{4, 5} + s1b, s2b := lendToRight(s1, s2, len(s1)) + require.Equal(t, []int{}, s1b) + require.Equal(t, []int{0, 0, 0}, s1b[:oldlen]) + require.Equal(t, []int{1, 2, 3, 4, 5}, s2b) + } + }) + + t.Run("move some elements", func(t *testing.T) { + s1 := []int{1, 2, 3} + oldlen := len(s1) + s2 := []int{4, 5} + s1b, s2b := lendToRight(s1, s2, 2) + require.Equal(t, []int{1}, s1b) + require.Equal(t, []int{1, 0, 0}, s1b[:oldlen]) + require.Equal(t, []int{2, 3, 4, 5}, s2b) + }) +} + +func TestSliceMoveFromRightToLeft(t *testing.T) { + t.Parallel() + + t.Run("count is out of range", func(t *testing.T) { + assert.Panics(t, func() { _, _ = borrowFromRight([]int{}, []int{}, 1) }) + + assert.Panics(t, func() { _, _ = borrowFromRight([]int{}, []int{1}, 2) }) + }) + + t.Run("move 0 element", func(t *testing.T) { + // right is empty slice + { + s1 := []int{3, 4} + s2 := []int{} + s1b, s2b := borrowFromRight(s1, s2, 0) + require.Equal(t, []int{3, 4}, s1b) + require.Equal(t, []int{}, s2b) + } + + // right is non-empty slice + { + s1 := []int{1, 2, 3} + p1 := &s1[0] + s2 := []int{4, 5} + p2 := &s2[0] + s1b, s2b := borrowFromRight(s1, s2, 0) + require.Equal(t, []int{1, 2, 3}, s1b) + require.Equal(t, p1, &s1b[0]) + require.Equal(t, []int{4, 5}, s2b) + require.Equal(t, p2, &s2b[0]) + } + }) + + t.Run("move all element", func(t *testing.T) { + // left is empty slice + { + s1 := []int{} + s2 := []int{1, 2, 3} + oldlen := len(s2) + s1b, s2b := borrowFromRight(s1, s2, len(s2)) + require.Equal(t, []int{1, 2, 3}, s1b) + require.Equal(t, []int{}, s2b) + require.Equal(t, []int{0, 0, 0}, s2b[:oldlen]) + } + + // left is non-empty slice + { + s1 := []int{1, 2, 3} + s2 := []int{4, 5} + oldlen := len(s2) + s1b, s2b := borrowFromRight(s1, s2, len(s2)) + require.Equal(t, []int{1, 2, 3, 4, 5}, s1b) + require.Equal(t, []int{}, s2b) + require.Equal(t, []int{0, 0}, s2b[:oldlen]) + } + }) + + t.Run("move some element", func(t *testing.T) { + s1 := []int{1, 2} + s2 := []int{3, 4, 5} + oldlen := len(s2) + s1b, s2b := borrowFromRight(s1, s2, 2) + require.Equal(t, []int{1, 2, 3, 4}, s1b) + require.Equal(t, []int{5}, s2b) + require.Equal(t, []int{5, 0, 0}, s2b[:oldlen]) + }) +} From bdd2ecbd917be2609121bff3d889a39f5c3e6556 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Tue, 4 Mar 2025 09:54:08 -0600 Subject: [PATCH 2/2] Use newly added util funcs to simplify slab ops This commit uses slice util functions to move elements in slab operations, such as Split(), Merge(), LendToRight(), and BorrowFromRight(). This simplifies code and reduces risk of problems such as memory leak caused by not freeing elements. --- array_data_slab.go | 47 ++++++++++++++----------------- array_metadata_slab.go | 55 ++++++++++++++++++------------------- map_elements_hashkey.go | 61 +++++++++++++++++++---------------------- map_metadata_slab.go | 56 ++++++++++++++++++------------------- 4 files changed, 103 insertions(+), 116 deletions(-) diff --git a/array_data_slab.go b/array_data_slab.go index 00ec6c1..930db90 100644 --- a/array_data_slab.go +++ b/array_data_slab.go @@ -187,7 +187,12 @@ func (a *ArrayDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { leftSize += elemSize } - // Construct right slab + // Split elements + var rightElements []Storable + a.elements, rightElements = split(a.elements, leftCount) + + // Create right slab + sID, err := storage.GenerateSlabID(a.header.slabID.address) if err != nil { // Wrap err as external error (if needed) because err is returned by SlabStorage interface. @@ -199,20 +204,18 @@ func (a *ArrayDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { ), ) } - rightSlabCount := len(a.elements) - leftCount + rightSlab := &ArrayDataSlab{ header: ArraySlabHeader{ slabID: sID, size: arrayDataSlabPrefixSize + dataSize - leftSize, - count: uint32(rightSlabCount), + count: uint32(len(rightElements)), }, - next: a.next, + next: a.next, + elements: rightElements, } - rightSlab.elements = slices.Clone(a.elements[leftCount:]) - // Modify left (original) slab - a.elements = slices.Delete(a.elements, leftCount, len(a.elements)) a.header.size = arrayDataSlabPrefixSize + leftSize a.header.count = uint32(leftCount) a.next = rightSlab.header.slabID @@ -222,7 +225,7 @@ func (a *ArrayDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { func (a *ArrayDataSlab) Merge(slab Slab) error { rightSlab := slab.(*ArrayDataSlab) - a.elements = append(a.elements, rightSlab.elements...) + a.elements = merge(a.elements, rightSlab.elements) a.header.size = a.header.size + rightSlab.header.size - arrayDataSlabPrefixSize a.header.count += rightSlab.header.count a.next = rightSlab.next @@ -237,6 +240,7 @@ func (a *ArrayDataSlab) LendToRight(slab Slab) error { count := a.header.count + rightSlab.header.count size := a.header.size + rightSlab.header.size + oldLeftCount := a.header.count leftCount := a.header.count leftSize := a.header.size @@ -252,22 +256,15 @@ func (a *ArrayDataSlab) LendToRight(slab Slab) error { leftCount-- } - // Update the right slab - - // Prepend elements from the left slab to the right slab - rightSlab.elements = slices.Insert( - rightSlab.elements, - 0, - a.elements[leftCount:]..., - ) + // Move elements + moveCount := oldLeftCount - leftCount + a.elements, rightSlab.elements = lendToRight(a.elements, rightSlab.elements, int(moveCount)) + // Update right slab rightSlab.header.size = size - leftSize rightSlab.header.count = count - leftCount // Update left slab - // NOTE: clear to prevent memory leak - clear(a.elements[leftCount:]) - a.elements = a.elements[:leftCount] a.header.size = leftSize a.header.count = leftCount @@ -281,6 +278,7 @@ func (a *ArrayDataSlab) BorrowFromRight(slab Slab) error { count := a.header.count + rightSlab.header.count size := a.header.size + rightSlab.header.size + oldLeftCount := a.header.count leftCount := a.header.count leftSize := a.header.size @@ -300,20 +298,15 @@ func (a *ArrayDataSlab) BorrowFromRight(slab Slab) error { leftCount++ } - rightStartIndex := leftCount - a.header.count + // Move elements + moveCount := leftCount - oldLeftCount + a.elements, rightSlab.elements = borrowFromRight(a.elements, rightSlab.elements, int(moveCount)) // Update left slab - a.elements = append(a.elements, rightSlab.elements[:rightStartIndex]...) a.header.size = leftSize a.header.count = leftCount // Update right slab - // TODO: copy elements to front instead? - // NOTE: prevent memory leak - for i := range rightStartIndex { - rightSlab.elements[i] = nil - } - rightSlab.elements = rightSlab.elements[rightStartIndex:] rightSlab.header.size = size - leftSize rightSlab.header.count = count - leftCount diff --git a/array_metadata_slab.go b/array_metadata_slab.go index 456a809..3c39c76 100644 --- a/array_metadata_slab.go +++ b/array_metadata_slab.go @@ -631,7 +631,7 @@ func (a *ArrayMetaDataSlab) Merge(slab Slab) error { leftSlabChildrenCount := len(a.childrenHeaders) rightSlab := slab.(*ArrayMetaDataSlab) - a.childrenHeaders = append(a.childrenHeaders, rightSlab.childrenHeaders...) + a.childrenHeaders = merge(a.childrenHeaders, rightSlab.childrenHeaders) a.header.size += rightSlab.header.size - arrayMetaDataSlabPrefixSize a.header.count += rightSlab.header.count @@ -661,7 +661,11 @@ func (a *ArrayMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { leftCount += a.childrenHeaders[i].count } - // Construct right slab + // Split childrenHeaders + var rightChildrenHeaders []ArraySlabHeader + a.childrenHeaders, rightChildrenHeaders = split(a.childrenHeaders, leftChildrenCount) + + // Create right slab sID, err := storage.GenerateSlabID(a.header.slabID.address) if err != nil { // Wrap err as external error (if needed) because err is returned by SlabStorage interface. @@ -676,10 +680,9 @@ func (a *ArrayMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { size: a.header.size - uint32(leftSize), count: a.header.count - leftCount, }, + childrenHeaders: rightChildrenHeaders, } - rightSlab.childrenHeaders = slices.Clone(a.childrenHeaders[leftChildrenCount:]) - rightSlab.childrenCountSum = make([]uint32, len(rightSlab.childrenHeaders)) countSum := uint32(0) for i := range rightSlab.childrenCountSum { @@ -688,7 +691,6 @@ func (a *ArrayMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { } // Modify left (original)slab - a.childrenHeaders = a.childrenHeaders[:leftChildrenCount] a.childrenCountSum = a.childrenCountSum[:leftChildrenCount] a.header.count = leftCount a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftSize) @@ -699,15 +701,13 @@ func (a *ArrayMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { func (a *ArrayMetaDataSlab) LendToRight(slab Slab) error { rightSlab := slab.(*ArrayMetaDataSlab) - childrenHeadersLen := len(a.childrenHeaders) + len(rightSlab.childrenHeaders) - leftChildrenHeadersLen := childrenHeadersLen / 2 + oldLeftChildrenHeaderCount := len(a.childrenHeaders) + childrenHeaderCount := len(a.childrenHeaders) + len(rightSlab.childrenHeaders) + leftChildrenHeaderCount := childrenHeaderCount / 2 - // Prepend child headers from the left slab to the right slab headers - rightSlab.childrenHeaders = slices.Insert( - rightSlab.childrenHeaders, - 0, - a.childrenHeaders[leftChildrenHeadersLen:]..., - ) + // Move elements + moveCount := oldLeftChildrenHeaderCount - leftChildrenHeaderCount + a.childrenHeaders, rightSlab.childrenHeaders = lendToRight(a.childrenHeaders, rightSlab.childrenHeaders, moveCount) // Rebuild right slab childrenCountSum rightSlab.childrenCountSum = make([]uint32, len(rightSlab.childrenHeaders)) @@ -725,41 +725,40 @@ func (a *ArrayMetaDataSlab) LendToRight(slab Slab) error { rightSlab.header.size = arrayMetaDataSlabPrefixSize + uint32(len(rightSlab.childrenHeaders))*arraySlabHeaderSize // Update left slab (original) - a.childrenHeaders = a.childrenHeaders[:leftChildrenHeadersLen] - a.childrenCountSum = a.childrenCountSum[:leftChildrenHeadersLen] + a.childrenCountSum = a.childrenCountSum[:leftChildrenHeaderCount] a.header.count = 0 for i := range a.childrenHeaders { a.header.count += a.childrenHeaders[i].count } - a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftChildrenHeadersLen)*arraySlabHeaderSize + a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftChildrenHeaderCount)*arraySlabHeaderSize return nil } func (a *ArrayMetaDataSlab) BorrowFromRight(slab Slab) error { - originalLeftSlabCountSum := a.header.count - originalLeftSlabHeaderLen := len(a.childrenHeaders) + oldLeftSlabCountSum := a.header.count + oldLeftChildrenHeaderCount := len(a.childrenHeaders) rightSlab := slab.(*ArrayMetaDataSlab) - childrenHeadersLen := len(a.childrenHeaders) + len(rightSlab.childrenHeaders) - leftSlabHeaderLen := childrenHeadersLen / 2 - rightSlabHeaderLen := childrenHeadersLen - leftSlabHeaderLen + childrenHeaderCount := len(a.childrenHeaders) + len(rightSlab.childrenHeaders) + leftChildrenHeaderCount := childrenHeaderCount / 2 - // Update left slab (original) - a.childrenHeaders = append(a.childrenHeaders, rightSlab.childrenHeaders[:leftSlabHeaderLen-len(a.childrenHeaders)]...) + // Move elements + moveCount := leftChildrenHeaderCount - oldLeftChildrenHeaderCount + a.childrenHeaders, rightSlab.childrenHeaders = borrowFromRight(a.childrenHeaders, rightSlab.childrenHeaders, moveCount) - countSum := originalLeftSlabCountSum - for i := originalLeftSlabHeaderLen; i < len(a.childrenHeaders); i++ { + // Update left slab (original) + countSum := oldLeftSlabCountSum + for i := oldLeftChildrenHeaderCount; i < len(a.childrenHeaders); i++ { countSum += a.childrenHeaders[i].count a.childrenCountSum = append(a.childrenCountSum, countSum) } a.header.count = countSum - a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftSlabHeaderLen)*arraySlabHeaderSize + a.header.size = arrayMetaDataSlabPrefixSize + uint32(leftChildrenHeaderCount)*arraySlabHeaderSize // Update right slab - rightSlab.childrenHeaders = rightSlab.childrenHeaders[len(rightSlab.childrenHeaders)-rightSlabHeaderLen:] rightSlab.childrenCountSum = rightSlab.childrenCountSum[:len(rightSlab.childrenHeaders)] countSum = uint32(0) @@ -768,7 +767,7 @@ func (a *ArrayMetaDataSlab) BorrowFromRight(slab Slab) error { rightSlab.childrenCountSum[i] = countSum } rightSlab.header.count = countSum - rightSlab.header.size = arrayMetaDataSlabPrefixSize + uint32(rightSlabHeaderLen)*arraySlabHeaderSize + rightSlab.header.size = arrayMetaDataSlabPrefixSize + uint32(len(rightSlab.childrenHeaders))*arraySlabHeaderSize return nil } diff --git a/map_elements_hashkey.go b/map_elements_hashkey.go index 2de8053..48a8f57 100644 --- a/map_elements_hashkey.go +++ b/map_elements_hashkey.go @@ -427,13 +427,10 @@ func (e *hkeyElements) Merge(elems elements) error { return NewSlabMergeError(fmt.Errorf("cannot merge elements of different types (%T, %T)", e, elems)) } - e.hkeys = append(e.hkeys, rElems.hkeys...) - e.elems = append(e.elems, rElems.elems...) + e.hkeys = merge(e.hkeys, rElems.hkeys) + e.elems = merge(e.elems, rElems.elems) e.size += rElems.Size() - hkeyElementsPrefixSize - // Set merged elements to nil to prevent memory leak. - clear(rElems.elems) - return nil } @@ -461,19 +458,22 @@ func (e *hkeyElements) Split() (elements, elements, error) { leftSize += elemSize } - // Create right slab elements - rightElements := &hkeyElements{level: e.level} - - rightElements.hkeys = slices.Clone(e.hkeys[leftCount:]) + // Split elements + var rightKeys []Digest + e.hkeys, rightKeys = split(e.hkeys, leftCount) - rightElements.elems = slices.Clone(e.elems[leftCount:]) + var rightElems []element + e.elems, rightElems = split(e.elems, leftCount) - rightElements.size = dataSize - leftSize + hkeyElementsPrefixSize + // Create right slab + rightElements := &hkeyElements{ + level: e.level, + hkeys: rightKeys, + elems: rightElems, + size: dataSize - leftSize + hkeyElementsPrefixSize, + } - e.hkeys = e.hkeys[:leftCount] - // NOTE: prevent memory leak - clear(e.elems[leftCount:]) - e.elems = e.elems[:leftCount] + // Update left slab e.size = hkeyElementsPrefixSize + leftSize return e, rightElements, nil @@ -494,6 +494,7 @@ func (e *hkeyElements) LendToRight(re elements) error { size := e.Size() + rightElements.Size() - hkeyElementsPrefixSize*2 + oldLeftCount := len(e.elems) leftCount := len(e.elems) leftSize := e.Size() - hkeyElementsPrefixSize @@ -509,16 +510,15 @@ func (e *hkeyElements) LendToRight(re elements) error { leftCount-- } - // Update the right elements - rightElements.hkeys = slices.Insert(rightElements.hkeys, 0, e.hkeys[leftCount:]...) - rightElements.elems = slices.Insert(rightElements.elems, 0, e.elems[leftCount:]...) + // Move elements + moveCount := oldLeftCount - leftCount + e.hkeys, rightElements.hkeys = lendToRight(e.hkeys, rightElements.hkeys, moveCount) + e.elems, rightElements.elems = lendToRight(e.elems, rightElements.elems, moveCount) + + // Update right slab rightElements.size = size - leftSize + hkeyElementsPrefixSize // Update left slab - // NOTE: prevent memory leak - clear(e.elems[leftCount:]) - e.hkeys = e.hkeys[:leftCount] - e.elems = e.elems[:leftCount] e.size = hkeyElementsPrefixSize + leftSize return nil @@ -539,6 +539,7 @@ func (e *hkeyElements) BorrowFromRight(re elements) error { size := e.Size() + rightElements.Size() - hkeyElementsPrefixSize*2 + oldLeftCount := len(e.elems) leftCount := len(e.elems) leftSize := e.Size() - hkeyElementsPrefixSize @@ -558,21 +559,15 @@ func (e *hkeyElements) BorrowFromRight(re elements) error { leftCount++ } - rightStartIndex := leftCount - len(e.elems) + // Move elements + moveCount := leftCount - oldLeftCount + e.hkeys, rightElements.hkeys = borrowFromRight(e.hkeys, rightElements.hkeys, moveCount) + e.elems, rightElements.elems = borrowFromRight(e.elems, rightElements.elems, moveCount) - // Update left elements - e.hkeys = append(e.hkeys, rightElements.hkeys[:rightStartIndex]...) - e.elems = append(e.elems, rightElements.elems[:rightStartIndex]...) + // Update left slab e.size = leftSize + hkeyElementsPrefixSize // Update right slab - // TODO: copy elements to front instead? - // NOTE: prevent memory leak - for i := range rightStartIndex { - rightElements.elems[i] = nil - } - rightElements.hkeys = rightElements.hkeys[rightStartIndex:] - rightElements.elems = rightElements.elems[rightStartIndex:] rightElements.size = size - leftSize + hkeyElementsPrefixSize return nil diff --git a/map_metadata_slab.go b/map_metadata_slab.go index 09f9e1c..4a9b1c2 100644 --- a/map_metadata_slab.go +++ b/map_metadata_slab.go @@ -590,7 +590,7 @@ func (m *MapMetaDataSlab) updateChildrenHeadersAfterMerge( func (m *MapMetaDataSlab) Merge(slab Slab) error { rightSlab := slab.(*MapMetaDataSlab) - m.childrenHeaders = append(m.childrenHeaders, rightSlab.childrenHeaders...) + m.childrenHeaders = merge(m.childrenHeaders, rightSlab.childrenHeaders) m.header.size += rightSlab.header.size - mapMetaDataSlabPrefixSize return nil @@ -611,19 +611,21 @@ func (m *MapMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { return nil, nil, wrapErrorfAsExternalErrorIfNeeded(err, fmt.Sprintf("failed to generate slab ID for address 0x%x", m.SlabID().address)) } - // Construct right slab + // Split children headers + var rightChildrenHeaders []MapSlabHeader + m.childrenHeaders, rightChildrenHeaders = split(m.childrenHeaders, leftChildrenCount) + + // Create right slab rightSlab := &MapMetaDataSlab{ header: MapSlabHeader{ slabID: sID, size: m.header.size - uint32(leftSize), - firstKey: m.childrenHeaders[leftChildrenCount].firstKey, + firstKey: rightChildrenHeaders[0].firstKey, }, + childrenHeaders: rightChildrenHeaders, } - rightSlab.childrenHeaders = slices.Clone(m.childrenHeaders[leftChildrenCount:]) - // Modify left (original) slab - m.childrenHeaders = m.childrenHeaders[:leftChildrenCount] m.header.size = mapMetaDataSlabPrefixSize + uint32(leftSize) return m, rightSlab, nil @@ -632,24 +634,21 @@ func (m *MapMetaDataSlab) Split(storage SlabStorage) (Slab, Slab, error) { func (m *MapMetaDataSlab) LendToRight(slab Slab) error { rightSlab := slab.(*MapMetaDataSlab) - childrenHeadersLen := len(m.childrenHeaders) + len(rightSlab.childrenHeaders) - leftChildrenHeadersLen := childrenHeadersLen / 2 - rightChildrenHeadersLen := childrenHeadersLen - leftChildrenHeadersLen + oldLeftChildrenHeaderCount := len(m.childrenHeaders) + childrenHeaderCount := len(m.childrenHeaders) + len(rightSlab.childrenHeaders) + leftChildrenHeaderCount := childrenHeaderCount / 2 + rightChildrenHeaderCount := childrenHeaderCount - leftChildrenHeaderCount - // Update right slab childrenHeaders by prepending borrowed children headers - rightSlab.childrenHeaders = slices.Insert( - rightSlab.childrenHeaders, - 0, - m.childrenHeaders[leftChildrenHeadersLen:]...) + // Move children headers + moveCount := oldLeftChildrenHeaderCount - leftChildrenHeaderCount + m.childrenHeaders, rightSlab.childrenHeaders = lendToRight(m.childrenHeaders, rightSlab.childrenHeaders, moveCount) - // Update right slab header - rightSlab.header.size = mapMetaDataSlabPrefixSize + uint32(rightChildrenHeadersLen)*mapSlabHeaderSize + // Update right slab + rightSlab.header.size = mapMetaDataSlabPrefixSize + uint32(rightChildrenHeaderCount)*mapSlabHeaderSize rightSlab.header.firstKey = rightSlab.childrenHeaders[0].firstKey // Update left slab (original) - m.childrenHeaders = m.childrenHeaders[:leftChildrenHeadersLen] - - m.header.size = mapMetaDataSlabPrefixSize + uint32(leftChildrenHeadersLen)*mapSlabHeaderSize + m.header.size = mapMetaDataSlabPrefixSize + uint32(leftChildrenHeaderCount)*mapSlabHeaderSize return nil } @@ -658,19 +657,20 @@ func (m *MapMetaDataSlab) BorrowFromRight(slab Slab) error { rightSlab := slab.(*MapMetaDataSlab) - childrenHeadersLen := len(m.childrenHeaders) + len(rightSlab.childrenHeaders) - leftSlabHeaderLen := childrenHeadersLen / 2 - rightSlabHeaderLen := childrenHeadersLen - leftSlabHeaderLen + oldLeftChildrenHeaderCount := len(m.childrenHeaders) + childrenHeaderCount := len(m.childrenHeaders) + len(rightSlab.childrenHeaders) + leftChildrenHeaderCount := childrenHeaderCount / 2 + rightChildrenHeaderCount := childrenHeaderCount - leftChildrenHeaderCount - // Update left slab (original) - m.childrenHeaders = append(m.childrenHeaders, rightSlab.childrenHeaders[:leftSlabHeaderLen-len(m.childrenHeaders)]...) + // Move children headers + moveCount := leftChildrenHeaderCount - oldLeftChildrenHeaderCount + m.childrenHeaders, rightSlab.childrenHeaders = borrowFromRight(m.childrenHeaders, rightSlab.childrenHeaders, moveCount) - m.header.size = mapMetaDataSlabPrefixSize + uint32(leftSlabHeaderLen)*mapSlabHeaderSize + // Update left slab (original) + m.header.size = mapMetaDataSlabPrefixSize + uint32(leftChildrenHeaderCount)*mapSlabHeaderSize // Update right slab - rightSlab.childrenHeaders = rightSlab.childrenHeaders[len(rightSlab.childrenHeaders)-rightSlabHeaderLen:] - - rightSlab.header.size = mapMetaDataSlabPrefixSize + uint32(rightSlabHeaderLen)*mapSlabHeaderSize + rightSlab.header.size = mapMetaDataSlabPrefixSize + uint32(rightChildrenHeaderCount)*mapSlabHeaderSize rightSlab.header.firstKey = rightSlab.childrenHeaders[0].firstKey return nil