/src/cpython/Objects/mimalloc/page-queue.c
Line | Count | Source |
1 | | /*---------------------------------------------------------------------------- |
2 | | Copyright (c) 2018-2024, Microsoft Research, Daan Leijen |
3 | | This is free software; you can redistribute it and/or modify it under the |
4 | | terms of the MIT license. A copy of the license can be found in the file |
5 | | "LICENSE" at the root of this distribution. |
6 | | -----------------------------------------------------------------------------*/ |
7 | | |
8 | | /* ----------------------------------------------------------- |
9 | | Definition of page queues for each block size |
10 | | ----------------------------------------------------------- */ |
11 | | |
12 | | #ifndef MI_IN_PAGE_C |
13 | | #error "this file should be included from 'page.c'" |
14 | | // include to help an IDE |
15 | | #include "mimalloc.h" |
16 | | #include "mimalloc/internal.h" |
17 | | #include "mimalloc/atomic.h" |
18 | | #endif |
19 | | |
20 | | /* ----------------------------------------------------------- |
21 | | Minimal alignment in machine words (i.e. `sizeof(void*)`) |
22 | | ----------------------------------------------------------- */ |
23 | | |
24 | | #if (MI_MAX_ALIGN_SIZE > 4*MI_INTPTR_SIZE) |
25 | | #error "define alignment for more than 4x word size for this platform" |
26 | | #elif (MI_MAX_ALIGN_SIZE > 2*MI_INTPTR_SIZE) |
27 | | #define MI_ALIGN4W // 4 machine words minimal alignment |
28 | | #elif (MI_MAX_ALIGN_SIZE > MI_INTPTR_SIZE) |
29 | | #define MI_ALIGN2W // 2 machine words minimal alignment |
30 | | #else |
31 | | // ok, default alignment is 1 word |
32 | | #endif |
33 | | |
34 | | |
35 | | /* ----------------------------------------------------------- |
36 | | Queue query |
37 | | ----------------------------------------------------------- */ |
38 | | |
39 | | |
40 | 0 | static inline bool mi_page_queue_is_huge(const mi_page_queue_t* pq) { |
41 | 0 | return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+sizeof(uintptr_t))); |
42 | 0 | } |
43 | | |
44 | 0 | static inline bool mi_page_queue_is_full(const mi_page_queue_t* pq) { |
45 | 0 | return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+(2*sizeof(uintptr_t)))); |
46 | 0 | } |
47 | | |
48 | 0 | static inline bool mi_page_queue_is_special(const mi_page_queue_t* pq) { |
49 | 0 | return (pq->block_size > MI_MEDIUM_OBJ_SIZE_MAX); |
50 | 0 | } |
51 | | |
52 | | /* ----------------------------------------------------------- |
53 | | Bins |
54 | | ----------------------------------------------------------- */ |
55 | | |
56 | | // Return the bin for a given field size. |
57 | | // Returns MI_BIN_HUGE if the size is too large. |
58 | | // We use `wsize` for the size in "machine word sizes", |
59 | | // i.e. byte size == `wsize*sizeof(void*)`. |
60 | 0 | static inline size_t mi_bin(size_t size) { |
61 | 0 | size_t wsize = _mi_wsize_from_size(size); |
62 | | #if defined(MI_ALIGN4W) |
63 | | if mi_likely(wsize <= 4) { |
64 | | return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes |
65 | | } |
66 | | #elif defined(MI_ALIGN2W) |
67 | 0 | if mi_likely(wsize <= 8) { |
68 | 0 | return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes |
69 | 0 | } |
70 | | #else |
71 | | if mi_likely(wsize <= 8) { |
72 | | return (wsize == 0 ? 1 : wsize); |
73 | | } |
74 | | #endif |
75 | 0 | else if mi_unlikely(wsize > MI_MEDIUM_OBJ_WSIZE_MAX) { |
76 | 0 | return MI_BIN_HUGE; |
77 | 0 | } |
78 | 0 | else { |
79 | | #if defined(MI_ALIGN4W) |
80 | | if (wsize <= 16) { wsize = (wsize+3)&~3; } // round to 4x word sizes |
81 | | #endif |
82 | 0 | wsize--; |
83 | | // find the highest bit |
84 | 0 | const size_t b = (MI_SIZE_BITS - 1 - mi_clz(wsize)); // note: wsize != 0 |
85 | | // and use the top 3 bits to determine the bin (~12.5% worst internal fragmentation). |
86 | | // - adjust with 3 because we use do not round the first 8 sizes |
87 | | // which each get an exact bin |
88 | 0 | const size_t bin = ((b << 2) + ((wsize >> (b - 2)) & 0x03)) - 3; |
89 | 0 | mi_assert_internal(bin > 0 && bin < MI_BIN_HUGE); |
90 | 0 | return bin; |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | | |
95 | | |
96 | | /* ----------------------------------------------------------- |
97 | | Queue of pages with free blocks |
98 | | ----------------------------------------------------------- */ |
99 | | |
100 | 0 | size_t _mi_bin(size_t size) { |
101 | 0 | return mi_bin(size); |
102 | 0 | } |
103 | | |
104 | 0 | size_t _mi_bin_size(size_t bin) { |
105 | 0 | return _mi_heap_empty.pages[bin].block_size; |
106 | 0 | } |
107 | | |
108 | | // Good size for allocation |
109 | 0 | size_t mi_good_size(size_t size) mi_attr_noexcept { |
110 | 0 | if (size <= MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) { |
111 | 0 | return _mi_bin_size(mi_bin(size + MI_PADDING_SIZE)); |
112 | 0 | } |
113 | 0 | else if (size <= MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) { |
114 | 0 | return _mi_align_up(size + MI_PADDING_SIZE,_mi_os_page_size()); |
115 | 0 | } |
116 | 0 | else { |
117 | 0 | return size; |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | | #if (MI_DEBUG>1) |
122 | | static bool mi_page_queue_contains(mi_page_queue_t* queue, const mi_page_t* page) { |
123 | | mi_assert_internal(page != NULL); |
124 | | mi_page_t* list = queue->first; |
125 | | while (list != NULL) { |
126 | | mi_assert_internal(list->next == NULL || list->next->prev == list); |
127 | | mi_assert_internal(list->prev == NULL || list->prev->next == list); |
128 | | if (list == page) break; |
129 | | list = list->next; |
130 | | } |
131 | | return (list == page); |
132 | | } |
133 | | |
134 | | #endif |
135 | | |
136 | | #if (MI_DEBUG>1) |
137 | | static bool mi_heap_contains_queue(const mi_heap_t* heap, const mi_page_queue_t* pq) { |
138 | | return (pq >= &heap->pages[0] && pq <= &heap->pages[MI_BIN_FULL]); |
139 | | } |
140 | | #endif |
141 | | |
142 | 0 | static inline bool mi_page_is_large_or_huge(const mi_page_t* page) { |
143 | 0 | return (mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_huge(page)); |
144 | 0 | } |
145 | | |
146 | 0 | static size_t mi_page_bin(const mi_page_t* page) { |
147 | 0 | const size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page)))); |
148 | 0 | mi_assert_internal(bin <= MI_BIN_FULL); |
149 | 0 | return bin; |
150 | 0 | } |
151 | | |
152 | | // returns the page bin without using MI_BIN_FULL for statistics |
153 | 0 | size_t _mi_page_stats_bin(const mi_page_t* page) { |
154 | 0 | const size_t bin = (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page))); |
155 | 0 | mi_assert_internal(bin <= MI_BIN_HUGE); |
156 | 0 | return bin; |
157 | 0 | } |
158 | | |
159 | 0 | static mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t* page) { |
160 | 0 | mi_assert_internal(heap!=NULL); |
161 | 0 | const size_t bin = mi_page_bin(page); |
162 | 0 | mi_page_queue_t* pq = &heap->pages[bin]; |
163 | 0 | mi_assert_internal((mi_page_block_size(page) == pq->block_size) || |
164 | 0 | (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(pq)) || |
165 | 0 | (mi_page_is_in_full(page) && mi_page_queue_is_full(pq))); |
166 | 0 | return pq; |
167 | 0 | } |
168 | | |
169 | 0 | static mi_page_queue_t* mi_page_queue_of(const mi_page_t* page) { |
170 | 0 | mi_heap_t* heap = mi_page_heap(page); |
171 | 0 | mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page); |
172 | 0 | mi_assert_expensive(mi_page_queue_contains(pq, page)); |
173 | 0 | return pq; |
174 | 0 | } |
175 | | |
176 | | // The current small page array is for efficiency and for each |
177 | | // small size (up to 256) it points directly to the page for that |
178 | | // size without having to compute the bin. This means when the |
179 | | // current free page queue is updated for a small bin, we need to update a |
180 | | // range of entries in `_mi_page_small_free`. |
181 | 0 | static inline void mi_heap_queue_first_update(mi_heap_t* heap, const mi_page_queue_t* pq) { |
182 | 0 | mi_assert_internal(mi_heap_contains_queue(heap,pq)); |
183 | 0 | const size_t size = pq->block_size; |
184 | 0 | if (size > MI_SMALL_SIZE_MAX) return; |
185 | | |
186 | 0 | mi_page_t* page = pq->first; |
187 | 0 | if (pq->first == NULL) page = (mi_page_t*)&_mi_page_empty; |
188 | | |
189 | | // find index in the right direct page array |
190 | 0 | const size_t idx = _mi_wsize_from_size(size); |
191 | 0 | mi_page_t** const pages_free = heap->pages_free_direct; |
192 | 0 | if (pages_free[idx] == page) return; // already set |
193 | | |
194 | | // find start slot |
195 | 0 | size_t start; |
196 | 0 | if (idx<=1) { |
197 | 0 | start = 0; |
198 | 0 | } |
199 | 0 | else { |
200 | | // find previous size; due to minimal alignment upto 3 previous bins may need to be skipped |
201 | 0 | mi_assert_internal(pq > &heap->pages[0]); // since idx > 1 |
202 | 0 | size_t bin = mi_bin(size); |
203 | 0 | const mi_page_queue_t* prev = pq - 1; |
204 | 0 | while( bin == mi_bin(prev->block_size) && prev > &heap->pages[0]) { |
205 | 0 | prev--; |
206 | 0 | } |
207 | 0 | start = 1 + _mi_wsize_from_size(prev->block_size); |
208 | 0 | if (start > idx) start = idx; |
209 | 0 | } |
210 | | |
211 | | // set size range to the right page |
212 | 0 | mi_assert(start <= idx); |
213 | 0 | for (size_t sz = start; sz <= idx; sz++) { |
214 | 0 | pages_free[sz] = page; |
215 | 0 | } |
216 | 0 | } |
217 | | |
218 | | /* |
219 | | static bool mi_page_queue_is_empty(mi_page_queue_t* queue) { |
220 | | return (queue->first == NULL); |
221 | | } |
222 | | */ |
223 | | |
224 | 0 | static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) { |
225 | 0 | mi_assert_internal(page != NULL); |
226 | 0 | mi_assert_expensive(mi_page_queue_contains(queue, page)); |
227 | 0 | mi_assert_internal(mi_page_block_size(page) == queue->block_size || |
228 | 0 | (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(queue)) || |
229 | 0 | (mi_page_is_in_full(page) && mi_page_queue_is_full(queue))); |
230 | 0 | mi_heap_t* heap = mi_page_heap(page); |
231 | |
|
232 | 0 | if (page->prev != NULL) page->prev->next = page->next; |
233 | 0 | if (page->next != NULL) page->next->prev = page->prev; |
234 | 0 | if (page == queue->last) queue->last = page->prev; |
235 | 0 | if (page == queue->first) { |
236 | 0 | queue->first = page->next; |
237 | | // update first |
238 | 0 | mi_assert_internal(mi_heap_contains_queue(heap, queue)); |
239 | 0 | mi_heap_queue_first_update(heap,queue); |
240 | 0 | } |
241 | 0 | heap->page_count--; |
242 | 0 | page->next = NULL; |
243 | 0 | page->prev = NULL; |
244 | | // mi_atomic_store_ptr_release(mi_atomic_cast(void*, &page->heap), NULL); |
245 | 0 | mi_page_set_in_full(page,false); |
246 | 0 | } |
247 | | |
248 | | |
249 | | #if MI_DEBUG >= 3 |
250 | | static bool mi_page_queue_is_consistent(const mi_page_queue_t* queue); |
251 | | #endif |
252 | | |
253 | 0 | static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) { |
254 | 0 | mi_assert_internal(mi_page_heap(page) == heap); |
255 | 0 | mi_assert_internal(!mi_page_queue_contains(queue, page)); |
256 | | #if MI_HUGE_PAGE_ABANDON |
257 | | mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE); |
258 | | #endif |
259 | 0 | mi_assert_internal(mi_page_block_size(page) == queue->block_size || |
260 | 0 | (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(queue)) || |
261 | 0 | (mi_page_is_in_full(page) && mi_page_queue_is_full(queue))); |
262 | |
|
263 | 0 | mi_page_set_in_full(page, mi_page_queue_is_full(queue)); |
264 | | // mi_atomic_store_ptr_release(mi_atomic_cast(void*, &page->heap), heap); |
265 | 0 | page->next = queue->first; |
266 | 0 | page->prev = NULL; |
267 | 0 | if (queue->first != NULL) { |
268 | 0 | mi_assert_internal(queue->first->prev == NULL); |
269 | 0 | queue->first->prev = page; |
270 | 0 | queue->first = page; |
271 | 0 | } |
272 | 0 | else { |
273 | 0 | queue->first = queue->last = page; |
274 | 0 | } |
275 | | |
276 | | // update direct |
277 | 0 | mi_heap_queue_first_update(heap, queue); |
278 | 0 | heap->page_count++; |
279 | | |
280 | | // [specbot Q-NEW-1] first/last must be both null or both non-null (L1, O(1)) |
281 | 0 | mi_assert_internal((queue->first == NULL) == (queue->last == NULL)); |
282 | | // [specbot Q-NEW-2] last element must have no next (L1, O(1)) |
283 | 0 | mi_assert_internal(queue->last == NULL || queue->last->next == NULL); |
284 | 0 | mi_assert_expensive(mi_page_queue_is_consistent(queue)); |
285 | 0 | } |
286 | | |
287 | | #if MI_DEBUG >= 3 |
288 | | // [specbot Q-NEW-3] Verify doubly-linked list forward/backward consistency (L2, O(n)) |
289 | | static bool mi_page_queue_is_consistent(const mi_page_queue_t* queue) { |
290 | | if (queue->first == NULL) return (queue->last == NULL); |
291 | | if (queue->last == NULL) return false; |
292 | | // forward: first -> ... -> last |
293 | | const mi_page_t* p = queue->first; |
294 | | const mi_page_t* prev = NULL; |
295 | | size_t count = 0; |
296 | | while (p != NULL) { |
297 | | mi_assert_internal(p->prev == prev); |
298 | | prev = p; |
299 | | p = p->next; |
300 | | count++; |
301 | | if (count > 100000) return false; // cycle guard |
302 | | } |
303 | | mi_assert_internal(prev == queue->last); |
304 | | return true; |
305 | | } |
306 | | #endif |
307 | | |
308 | 0 | static void mi_page_queue_move_to_front(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) { |
309 | 0 | mi_assert_internal(mi_page_heap(page) == heap); |
310 | 0 | mi_assert_internal(mi_page_queue_contains(queue, page)); |
311 | 0 | if (queue->first == page) return; |
312 | 0 | mi_page_queue_remove(queue, page); |
313 | 0 | mi_page_queue_push(heap, queue, page); |
314 | 0 | mi_assert_internal(queue->first == page); |
315 | 0 | } |
316 | | |
317 | 0 | static void mi_page_queue_enqueue_from_ex(mi_page_queue_t* to, mi_page_queue_t* from, bool enqueue_at_end, mi_page_t* page) { |
318 | 0 | mi_assert_internal(page != NULL); |
319 | 0 | mi_assert_expensive(mi_page_queue_contains(from, page)); |
320 | 0 | mi_assert_expensive(!mi_page_queue_contains(to, page)); |
321 | 0 | const size_t bsize = mi_page_block_size(page); |
322 | 0 | MI_UNUSED(bsize); |
323 | 0 | mi_assert_internal((bsize == to->block_size && bsize == from->block_size) || |
324 | 0 | (bsize == to->block_size && mi_page_queue_is_full(from)) || |
325 | 0 | (bsize == from->block_size && mi_page_queue_is_full(to)) || |
326 | 0 | (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(to)) || |
327 | 0 | (mi_page_is_large_or_huge(page) && mi_page_queue_is_full(to))); |
328 | |
|
329 | 0 | mi_heap_t* heap = mi_page_heap(page); |
330 | | |
331 | | // delete from `from` |
332 | 0 | if (page->prev != NULL) page->prev->next = page->next; |
333 | 0 | if (page->next != NULL) page->next->prev = page->prev; |
334 | 0 | if (page == from->last) from->last = page->prev; |
335 | 0 | if (page == from->first) { |
336 | 0 | from->first = page->next; |
337 | | // update first |
338 | 0 | mi_assert_internal(mi_heap_contains_queue(heap, from)); |
339 | 0 | mi_heap_queue_first_update(heap, from); |
340 | 0 | } |
341 | | |
342 | | // insert into `to` |
343 | 0 | if (enqueue_at_end) { |
344 | | // enqueue at the end |
345 | 0 | page->prev = to->last; |
346 | 0 | page->next = NULL; |
347 | 0 | if (to->last != NULL) { |
348 | 0 | mi_assert_internal(heap == mi_page_heap(to->last)); |
349 | 0 | to->last->next = page; |
350 | 0 | to->last = page; |
351 | 0 | } |
352 | 0 | else { |
353 | 0 | to->first = page; |
354 | 0 | to->last = page; |
355 | 0 | mi_heap_queue_first_update(heap, to); |
356 | 0 | } |
357 | 0 | } |
358 | 0 | else { |
359 | 0 | if (to->first != NULL) { |
360 | | // enqueue at 2nd place |
361 | 0 | mi_assert_internal(heap == mi_page_heap(to->first)); |
362 | 0 | mi_page_t* next = to->first->next; |
363 | 0 | page->prev = to->first; |
364 | 0 | page->next = next; |
365 | 0 | to->first->next = page; |
366 | 0 | if (next != NULL) { |
367 | 0 | next->prev = page; |
368 | 0 | } |
369 | 0 | else { |
370 | 0 | to->last = page; |
371 | 0 | } |
372 | 0 | } |
373 | 0 | else { |
374 | | // enqueue at the head (singleton list) |
375 | 0 | page->prev = NULL; |
376 | 0 | page->next = NULL; |
377 | 0 | to->first = page; |
378 | 0 | to->last = page; |
379 | 0 | mi_heap_queue_first_update(heap, to); |
380 | 0 | } |
381 | 0 | } |
382 | |
|
383 | 0 | mi_page_set_in_full(page, mi_page_queue_is_full(to)); |
384 | 0 | } |
385 | | |
386 | 0 | static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) { |
387 | 0 | mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end */, page); |
388 | 0 | } |
389 | | |
390 | 0 | static void mi_page_queue_enqueue_from_full(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) { |
391 | | // note: we could insert at the front to increase reuse, but it slows down certain benchmarks (like `alloc-test`) |
392 | 0 | mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end of the `to` queue? */, page); |
393 | 0 | } |
394 | | |
395 | | // Only called from `mi_heap_absorb`. |
396 | 0 | size_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append) { |
397 | 0 | mi_assert_internal(mi_heap_contains_queue(heap,pq)); |
398 | 0 | mi_assert_internal(pq->block_size == append->block_size); |
399 | |
|
400 | 0 | if (append->first==NULL) return 0; |
401 | | |
402 | | // set append pages to new heap and count |
403 | 0 | size_t count = 0; |
404 | 0 | for (mi_page_t* page = append->first; page != NULL; page = page->next) { |
405 | | // inline `mi_page_set_heap` to avoid wrong assertion during absorption; |
406 | | // in this case it is ok to be delayed freeing since both "to" and "from" heap are still alive. |
407 | 0 | mi_atomic_store_release(&page->xheap, (uintptr_t)heap); |
408 | | // set the flag to delayed free (not overriding NEVER_DELAYED_FREE) which has as a |
409 | | // side effect that it spins until any DELAYED_FREEING is finished. This ensures |
410 | | // that after appending only the new heap will be used for delayed free operations. |
411 | 0 | _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, false); |
412 | 0 | count++; |
413 | 0 | } |
414 | |
|
415 | 0 | if (pq->last==NULL) { |
416 | | // take over afresh |
417 | 0 | mi_assert_internal(pq->first==NULL); |
418 | 0 | pq->first = append->first; |
419 | 0 | pq->last = append->last; |
420 | 0 | mi_heap_queue_first_update(heap, pq); |
421 | 0 | } |
422 | 0 | else { |
423 | | // append to end |
424 | 0 | mi_assert_internal(pq->last!=NULL); |
425 | 0 | mi_assert_internal(append->first!=NULL); |
426 | 0 | pq->last->next = append->first; |
427 | 0 | append->first->prev = pq->last; |
428 | 0 | pq->last = append->last; |
429 | 0 | } |
430 | 0 | return count; |
431 | 0 | } |