1 package memory
2
3 import (
4 "github.com/pkg/errors"
5 )
6
7 const (
8 minimumClassSize = MiB
9 maximumClassSize = 4 * GiB
10 memoryClassNumber = 7
11 )
12
13 var (
14 ErrInvalidMemoryClass = errors.New("invalid memory class")
15 ErrEarlyMerge = errors.New("not all children have been freed")
16 ErrEmptyPoolOperation = errors.New("operation on empty pool")
17 )
18
19
20
21
22
23 func GetMemoryClassType(s uint64) classType {
24 s = (s - 1) >> 20
25 memCls := uint32(0)
26 for s > 0 {
27 s = s >> 2
28 memCls++
29 }
30 return classType(memCls)
31 }
32
33
34 func GetMemoryClassSize(memCls classType) (uint64, error) {
35 if memCls >= memoryClassNumber {
36 return 0, ErrInvalidMemoryClass
37 }
38 return minimumClassSize << (2 * memCls), nil
39 }
40
41
42 type region struct {
43
44 parent *region
45 class classType
46
47 offset uint64
48 }
49
50
51 type memoryPool struct {
52 free map[uint64]*region
53 busy map[uint64]*region
54 }
55
56
57
58
59
60
61
62
63
64
65
66
67
68 type PoolAllocator struct {
69 pools [memoryClassNumber]*memoryPool
70 }
71
72 var _ MappedRegion = ®ion{}
73 var _ Allocator = &PoolAllocator{}
74
75 func (r *region) Offset() uint64 {
76 return r.offset
77 }
78
79 func (r *region) Size() uint64 {
80 sz, err := GetMemoryClassSize(r.class)
81 if err != nil {
82 panic(err)
83 }
84 return sz
85 }
86
87 func (r *region) Type() classType {
88 return r.class
89 }
90
91 func newEmptyMemoryPool() *memoryPool {
92 return &memoryPool{
93 free: make(map[uint64]*region),
94 busy: make(map[uint64]*region),
95 }
96 }
97
98 func NewPoolMemoryAllocator() PoolAllocator {
99 pa := PoolAllocator{}
100 p := newEmptyMemoryPool()
101
102 p.free[0] = ®ion{
103 class: memoryClassNumber - 1,
104 offset: 0,
105 }
106 pa.pools[memoryClassNumber-1] = p
107 return pa
108 }
109
110
111
112
113
114 func (pa *PoolAllocator) Allocate(size uint64) (MappedRegion, error) {
115 memCls := GetMemoryClassType(size)
116 if memCls >= memoryClassNumber {
117 return nil, ErrInvalidMemoryClass
118 }
119
120
121 nextCls, nextOffset, err := pa.findNextOffset(memCls)
122 if err != nil {
123 return nil, err
124 }
125
126
127 if nextCls != memCls {
128 if err := pa.split(memCls); err != nil {
129 if err == ErrInvalidMemoryClass {
130 return nil, ErrNotEnoughSpace
131 }
132 return nil, err
133 }
134 }
135
136 if err := pa.markBusy(memCls, nextOffset); err != nil {
137 return nil, err
138 }
139
140
141
142 if r := pa.pools[memCls].busy[nextOffset]; r != nil {
143 return r, nil
144 }
145
146 return nil, ErrNotEnoughSpace
147 }
148
149
150
151 func (pa *PoolAllocator) Release(reg MappedRegion) error {
152 mp := pa.pools[reg.Type()]
153 if mp == nil {
154 return ErrEmptyPoolOperation
155 }
156
157 err := pa.markFree(reg.Type(), reg.Offset())
158 if err != nil {
159 return err
160 }
161
162 n := mp.free[reg.Offset()]
163 if n == nil {
164 return ErrNotAllocated
165 }
166 if err := pa.merge(n.parent); err != nil {
167 if err != ErrEarlyMerge {
168 return err
169 }
170 }
171 return nil
172 }
173
174
175 func (pa *PoolAllocator) findNextOffset(memCls classType) (classType, uint64, error) {
176 for mc := memCls; mc < memoryClassNumber; mc++ {
177 pi := pa.pools[mc]
178 if pi == nil || len(pi.free) == 0 {
179 continue
180 }
181
182 target := uint64(maximumClassSize)
183 for offset := range pi.free {
184 if offset < target {
185 target = offset
186 }
187 }
188 return mc, target, nil
189 }
190 return 0, 0, ErrNotEnoughSpace
191 }
192
193
194 func (pa *PoolAllocator) split(clsType classType) error {
195 nextClsType := clsType + 1
196 if nextClsType >= memoryClassNumber {
197 return ErrInvalidMemoryClass
198 }
199
200 nextPool := pa.pools[nextClsType]
201 if nextPool == nil {
202 nextPool = newEmptyMemoryPool()
203 pa.pools[nextClsType] = nextPool
204 }
205
206 cls, offset, err := pa.findNextOffset(nextClsType)
207 if err != nil {
208 return err
209 }
210
211 if cls != nextClsType {
212 if err := pa.split(nextClsType); err != nil {
213 return err
214 }
215 }
216
217 if err := pa.markBusy(nextClsType, offset); err != nil {
218 return err
219 }
220
221
222 clsSize, _ := GetMemoryClassSize(clsType)
223
224 nextReg := nextPool.busy[offset]
225 if nextReg == nil {
226 return ErrNotAllocated
227 }
228
229
230 cp := pa.pools[clsType]
231 if cp == nil {
232 cp = newEmptyMemoryPool()
233 pa.pools[clsType] = cp
234 }
235
236 for i := uint64(0); i < 4; i++ {
237 offset := nextReg.offset + i*clsSize
238 reg := ®ion{
239 parent: nextReg,
240 class: clsType,
241 offset: offset,
242 }
243 cp.free[offset] = reg
244 }
245 return nil
246 }
247
248 func (pa *PoolAllocator) merge(parent *region) error {
249
250 if parent == nil {
251 return nil
252 }
253
254 childCls := parent.class - 1
255 childPool := pa.pools[childCls]
256
257 if childPool == nil {
258 return pa.merge(parent.parent)
259 }
260
261 childSize, err := GetMemoryClassSize(childCls)
262 if err != nil {
263 return err
264 }
265
266
267 var children []*region
268 for i := uint64(0); i < 4; i++ {
269 child, free := childPool.free[parent.offset+i*childSize]
270 if !free {
271 return ErrEarlyMerge
272 }
273 children = append(children, child)
274 }
275
276
277 for _, child := range children {
278 delete(childPool.free, child.offset)
279 }
280
281 if err := pa.markFree(parent.class, parent.offset); err != nil {
282 return err
283 }
284
285 return pa.merge(parent.parent)
286 }
287
288
289 func (pa *PoolAllocator) markFree(memCls classType, offset uint64) error {
290 clsPool := pa.pools[memCls]
291 if clsPool == nil {
292 return ErrEmptyPoolOperation
293 }
294
295 if reg, exists := clsPool.busy[offset]; exists {
296 clsPool.free[offset] = reg
297 delete(clsPool.busy, offset)
298 return nil
299 }
300 return ErrNotAllocated
301 }
302
303
304 func (pa *PoolAllocator) markBusy(memCls classType, offset uint64) error {
305 clsPool := pa.pools[memCls]
306 if clsPool == nil {
307 return ErrEmptyPoolOperation
308 }
309
310 if reg, exists := clsPool.free[offset]; exists {
311 clsPool.busy[offset] = reg
312 delete(clsPool.free, offset)
313 return nil
314 }
315 return ErrNotAllocated
316 }
317
View as plain text