github.com/haraldrudell/parl@v0.4.176/pslices/slice-away-append.go (about) 1 /* 2 © 2024–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package pslices 7 8 import ( 9 "unsafe" 10 ) 11 12 // [SliceAwayAppend] [SliceAwayAppend1] do not zero-out obsolete slice elements 13 // - [SetLength] noZero 14 const NoZeroOut = true 15 16 // [SliceAwayAppend] [SliceAwayAppend1] do zero-out obsolete slice elements 17 // - [SetLength] noZero 18 const DoZeroOut = false 19 20 // SliceAwayAppend avoids allocations when a slice is 21 // sliced away from the beginning while being appended to at the end 22 // - sliceAway: the slice of active values, sliced away and appended to 23 // - slice0: the original sliceAway 24 // - values: values that should be appended to sliceAway 25 // - noZeroOut NoZeroOut: do not set unused element to zero-value. 26 // Slices retaining values containing pointers in unused elements 27 // is a temporary memory leak. Zero-out prevents this memory leak 28 // - by storing the initial slice along with the slice-away slice, 29 // the initial slice can be retrieved which may avoid allocations 30 // - SliceAwayAppend takes pointer to slice so it can 31 // update slicedAway and slice0 32 // - There are three outcomes for a slice-away append: 33 // - — 1 realloc: the result is larger than the underlying array 34 // - — 2 append: appending fits slicedAway capacity 35 // - — 3 copy: appending to SlicedAway fits the underlying array but 36 // not slicedAway capacity 37 func SliceAwayAppend[T any](slicedAway, slice0 *[]T, values []T, noZeroOut ...bool) { 38 // awaySlice is slicedAway prior to append 39 var awaySlice = *slicedAway 40 // sliceZero is slice0 pointer and capacity from make 41 var sliceZero = *slice0 42 43 // useRegularAppend indicates that a normal append 44 // should be used 45 // - true for case 1 realloc and case 2 append 46 // - false for case 3 copy 47 var valueLength = len(awaySlice) + len(values) 48 49 // 2 append: appending fits slicedAway capacity 50 if valueLength <= cap(awaySlice) { 51 *slicedAway = append(awaySlice, values...) 52 return 53 } 54 55 // 1 realloc: the result is larger than the underlying array 56 if valueLength > cap(sliceZero) { 57 awaySlice = append(awaySlice, values...) 58 // update sliceAway and slice0 59 *slicedAway = awaySlice 60 *slice0 = awaySlice 61 return 62 } 63 // 3 copy: appending to SlicedAway fits the underlying array but 64 // not slicedAway capacity 65 // - slicedAway values need to be copied to 66 // the beginning of sliceZero 67 68 // re-use slice0 69 // - cannot arbitrary set length to do copy 70 // - therefore, set length to zero and double-append 71 if len(sliceZero) > 0 { 72 sliceZero = sliceZero[:0] 73 } 74 // newSlice is at beginning of array and has the new aggregate length 75 // - appends do not cause allocation 76 var newSlice = append(append(sliceZero, awaySlice...), values...) 77 *slicedAway = newSlice 78 79 // zero-out if not disabled 80 if len(noZeroOut) > 0 && noZeroOut[0] { 81 return // no zero-out 82 } 83 zeroOut(newSlice, awaySlice) 84 } 85 86 // SliceAwayAppend1 avoids allocations when a slice is 87 // sliced away from the beginning and appended to at the end 88 // - sliceAway: the slice of active values, sliced away and appended to 89 // - slice0: the original sliceAway 90 // - value: the value that should be appended to sliceAway 91 // - by storing the initial slice along with the slice-away slice, 92 // the initial slice can be retrieved which may avoid allocations 93 // - SliceAwayAppend avoid such allocations based on two pointers to slice 94 func SliceAwayAppend1[T any](slicedAway, slice0 *[]T, value T, noZeroOut ...bool) { 95 // awaySlice is slicedAway prior to append 96 var awaySlice = *slicedAway 97 // sliceZero is slice0 length and capacity prior to append 98 var sliceZero = *slice0 99 100 // useRegularAppend indicates that a normal append 101 // should be used 102 // - true for case 1 realloc and case 2 append 103 // - false for case 3 copy 104 var valueLength = len(awaySlice) + 1 105 106 // 2 append: appending fits slicedAway capacity 107 if valueLength <= cap(awaySlice) { 108 *slicedAway = append(awaySlice, value) 109 return 110 } 111 112 // 1 realloc: the result is larger than the underlying array 113 if valueLength > cap(sliceZero) { 114 awaySlice = append(awaySlice, value) 115 // update sliceAway and slice0 116 *slicedAway = awaySlice 117 *slice0 = awaySlice 118 return 119 } 120 // 3 copy: appending to SlicedAway fits the underlying array but 121 // not slicedAway capacity 122 // - slicedAway values need to be copied to 123 // the beginning of sliceZero 124 125 // re-use slice0 126 // - cannot arbitrary set length to do copy 127 // - therefore, set length to zero and double-append 128 if len(sliceZero) > 0 { 129 sliceZero = sliceZero[:0] 130 } 131 // newSlice is at beginning of array and has the new aggregate length 132 // - appends do not cause allocation 133 var newSlice = append(append(sliceZero, awaySlice...), value) 134 *slicedAway = newSlice 135 136 // is zero-out disabled? 137 if len(noZeroOut) > 0 && noZeroOut[0] { 138 return // no zero-out 139 } 140 zeroOut(newSlice, awaySlice) 141 } 142 143 // zero-out emptied values 144 // - newSlice begins at the beginning of the underlying array and 145 // contains all the newly appended aggregate data 146 // - slicedAway is the previously slicedAway slice containing 147 // data from before the append 148 // - elements to zero out are those at end of slicedAway that 149 // are not part of newSlice 150 func zeroOut[T any](newSlice, slicedAway []T) { 151 152 // offset his how many elements slicedAway is off from newSlice 153 var offset, isValid = Offset(newSlice, slicedAway) 154 if !isValid { 155 return // some issue with slices 156 } 157 158 // number of elements to zero out at end of slicedAway 159 var elementCount int 160 // the first index not used in newSlice now 161 var newEnd = len(newSlice) 162 // the first index not used before in newSlice 163 var oldEnd = offset + len(slicedAway) 164 // newEnd is greater or equal, there are no element to zero out 165 if newEnd >= oldEnd { 166 return // no elements to zero-out 167 } 168 // number of elements to zero out at the end of slicedAway 169 elementCount = oldEnd - newEnd 170 // limit elementCount to length of slicedAway 171 if elementCount > len(slicedAway) { 172 elementCount = len(slicedAway) 173 } 174 175 // zero out elementCount elements at the end of slicedAway 176 var zeroValue T 177 for index := len(slicedAway) - elementCount; index < len(slicedAway); index++ { 178 slicedAway[index] = zeroValue 179 } 180 } 181 182 // Offset calculates how many items a slice-away slice is off 183 // from the initial slice 184 // - slice0 is a slice containing the beginning of the underlying array 185 // - slicedAway is a slice that has been sliced-off at the beginning 186 func Offset[T any](slice0, slicedAway []T) (offset int, isValid bool) { 187 188 // verify that operation is possible 189 // - slice0 slicedAway cannot be nil 190 if slice0 == nil || slicedAway == nil { 191 return // operation failed 192 } 193 194 // a pointer to a slice points to 3 values: 195 // - pointer to underlying array: pointer to array of elements 196 // - current slice length: int 197 // - current slice capacity: int 198 // - by casting uintptr to uint, pointer arithmetic becomes possible 199 200 // slice0p is the uint memory address of the underlying array of slice0 201 var slice0p = *((*uint)(unsafe.Pointer(&slice0))) 202 // slicedAwayp is the uint memory address of the underlying array of slicedAway 203 var slicedAwayp = *((*uint)(unsafe.Pointer(&slicedAway))) 204 205 // // slice0p 0x14000016160 206 // fmt.Fprintf(os.Stderr, "slice0p 0x%x\n", slice0p) 207 // // slicedAwayp 0x14000016170 208 // fmt.Fprintf(os.Stderr, "slicedAwayp 0x%x\n", slicedAwayp) 209 210 // slicedAway[0]: 3 211 // fmt.Fprintf(os.Stderr, "slicedAway[0]: %d\n", 212 // **(**int)(unsafe.Pointer(&slicedAway)), 213 // ) 214 215 // ensure slicedAway is from the same underlying array as slice0 216 if slicedAwayp < slice0p { 217 return // not same array 218 } 219 220 // determine element size in bytes 221 var a [2]T 222 var elementSize = 0 + // 223 (uint)((uintptr)(unsafe.Pointer(&a[1]))) - 224 (uint)((uintptr)(unsafe.Pointer(&a[0]))) 225 226 // int 64-bit elementSize 0x8 227 //fmt.Fprintf(os.Stderr, "int 64-bit elementSize 0x%x\n", elementSize) 228 229 // offset is how many elements have been sliced off from slice0 230 // - not negative 231 offset = int((slicedAwayp - slice0p) / elementSize) 232 isValid = 233 // slicedAwayp - slice0p must be even divisible by element size 234 slice0p+uint(offset)*elementSize == slicedAwayp && 235 // slicedAway and offset cannot beyond the end of slice0 236 offset+len(slicedAway) <= cap(slice0) 237 238 return 239 }