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