github.com/haraldrudell/parl@v0.4.176/pslices/shifter.go (about) 1 /* 2 © 2023–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 "github.com/haraldrudell/parl/perrors" 12 "github.com/haraldrudell/parl/plog" 13 ) 14 15 const ( 16 // - [ZeroFillingShifter] provided as argument to [NewShifter] causes 17 // freed elements to be set to the T zero-value. 18 // This prevents temporary memory leaks when: 19 // - T contains pointers and 20 // - previously used T elements remain in the slice’s underlying array 21 ZeroFillingShifter = true 22 ) 23 24 // Shifter implements append filtered by a capacity check 25 // - this avoids unnecessary slice re-allocations for when 26 // a slice is used as a buffer: 27 // - — items are sliced off at the beginning and 28 // - — new items appended at the end 29 type Shifter[T any] struct { 30 // Slice contains current elements 31 Slice []T 32 // initialSlice is the slice provided or from last re-allocating append 33 initialSlice []T 34 // if zeroFill [ZeroFillingShifter], slice element size in bytes 35 elementSize int 36 } 37 38 // NewShifter returns a slice container providing an Append that filters 39 // unnecessary allocations 40 // - if zeroFill is [ZeroFillingShifter] freed elements are set to zero-value 41 // - — this prevents temporary memory leaks when: 42 // - — elements contains pointers and 43 // - — previously used elements remain in the slice’s underlying array 44 func NewShifter[T any](slice []T, zeroFill ...bool) (shifter *Shifter[T]) { 45 var elementSize int 46 if len(zeroFill) > 0 && zeroFill[0] { 47 var s []T 48 if cap(slice) >= 2 { 49 s = slice[0:2] 50 } else { 51 s = make([]T, 2) 52 } 53 elementSize = // 54 int(uintptr(unsafe.Pointer(&s[1]))) - 55 int(uintptr(unsafe.Pointer(&s[0]))) 56 } 57 return &Shifter[T]{Slice: slice, initialSlice: slice, elementSize: elementSize} 58 } 59 60 // Append avoids unnecessary allocations if capacity is sufficient 61 func (s *Shifter[T]) Append(items ...T) (slice []T) { 62 if len(items) == 0 { 63 return s.Slice // noop return 64 } 65 66 var requiredCapacity = len(s.Slice) + len(items) 67 if requiredCapacity > cap(s.initialSlice) { 68 69 // if insufficient capacity, use regular append 70 s.Slice = append(s.Slice, items...) 71 } else { 72 73 // re-use initialSlice 74 var xSlice = s.Slice 75 s.Slice = append(append(s.initialSlice[:0], s.Slice...), items...) 76 77 // do zero fill 78 // - when xSlice was more off from initialSlice than len(items) 79 if s.elementSize > 0 && len(xSlice) > 0 { 80 s.zeroFill(s.Slice, xSlice) 81 } 82 } 83 s.initialSlice = s.Slice 84 slice = s.Slice 85 86 return 87 } 88 89 // zeroFill fills unused elements of now with zero-value 90 // - now and before are non-empty slices 91 // - before is expected to be a slice-result of now 92 // - the task is to possibly write zero-values to last elements of before 93 func (s *Shifter[T]) zeroFill(now, before []T) { 94 plog.D("now len %d before len %d", len(now), len(before)) 95 // first element of before as index of now 96 var beforeIndex int 97 var beforep = int(uintptr(unsafe.Pointer(&before[0]))) 98 var nowp = int(uintptr(unsafe.Pointer(&now[0]))) 99 beforeIndex = (beforep - nowp) / s.elementSize 100 101 // check beforeIndex 102 var capacity = cap(now) 103 plog.D("beforeIndex: %d cap %d", beforeIndex, capacity) 104 if beforeIndex < 0 || beforeIndex >= capacity { 105 panic(s.zeroFillError(now, before, capacity, beforeIndex)) 106 } 107 108 // zero-fill end of before 109 var firstUnused = len(now) - beforeIndex 110 plog.D("firstUnused: %d", firstUnused) 111 var t T 112 for firstUnused < len(before) { 113 before[firstUnused] = t 114 firstUnused++ 115 } 116 } 117 118 // zeroFillError returns an actionable error for a software deficiency 119 func (s *Shifter[T]) zeroFillError(now, before []T, capacity, beforeIndex int) (err error) { 120 var bSign string 121 var b = beforeIndex 122 if b < 0 { 123 bSign = "-" 124 b = -beforeIndex 125 } 126 err = perrors.ErrorfPF("bad zeroFill: now: len %d cap %d before: len %d cap %d index: %s0x%x", 127 len(now), capacity, 128 len(before), cap(before), 129 bSign, b, 130 ) 131 return 132 }