/src/cpython/Objects/mimalloc/page.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 | | The core of the allocator. Every segment contains |
10 | | pages of a certain block size. The main function |
11 | | exported is `mi_malloc_generic`. |
12 | | ----------------------------------------------------------- */ |
13 | | |
14 | | #include "mimalloc.h" |
15 | | #include "mimalloc/internal.h" |
16 | | #include "mimalloc/atomic.h" |
17 | | |
18 | | /* ----------------------------------------------------------- |
19 | | Definition of page queues for each block size |
20 | | ----------------------------------------------------------- */ |
21 | | |
22 | | #define MI_IN_PAGE_C |
23 | | #include "page-queue.c" |
24 | | #undef MI_IN_PAGE_C |
25 | | |
26 | | |
27 | | /* ----------------------------------------------------------- |
28 | | Page helpers |
29 | | ----------------------------------------------------------- */ |
30 | | |
31 | | // Index a block in a page |
32 | 0 | static inline mi_block_t* mi_page_block_at(const mi_page_t* page, void* page_start, size_t block_size, size_t i) { |
33 | 0 | MI_UNUSED(page); |
34 | 0 | mi_assert_internal(page != NULL); |
35 | 0 | mi_assert_internal(i <= page->reserved); |
36 | 0 | return (mi_block_t*)((uint8_t*)page_start + (i * block_size)); |
37 | 0 | } |
38 | | |
39 | | static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t size, mi_tld_t* tld); |
40 | | static bool mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld); |
41 | | |
42 | | #if (MI_DEBUG>=3) |
43 | | static size_t mi_page_list_count(mi_page_t* page, mi_block_t* head) { |
44 | | size_t count = 0; |
45 | | while (head != NULL) { |
46 | | mi_assert_internal(page == _mi_ptr_page(head)); |
47 | | count++; |
48 | | head = mi_block_next(page, head); |
49 | | } |
50 | | return count; |
51 | | } |
52 | | |
53 | | /* |
54 | | // Start of the page available memory |
55 | | static inline uint8_t* mi_page_area(const mi_page_t* page) { |
56 | | return _mi_page_start(_mi_page_segment(page), page, NULL); |
57 | | } |
58 | | */ |
59 | | |
60 | | static bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) { |
61 | | size_t psize; |
62 | | uint8_t* page_area = _mi_segment_page_start(_mi_page_segment(page), page, &psize); |
63 | | mi_block_t* start = (mi_block_t*)page_area; |
64 | | mi_block_t* end = (mi_block_t*)(page_area + psize); |
65 | | while(p != NULL) { |
66 | | if (p < start || p >= end) return false; |
67 | | p = mi_block_next(page, p); |
68 | | } |
69 | | #if MI_DEBUG>3 // generally too expensive to check this |
70 | | if (page->free_is_zero) { |
71 | | const size_t ubsize = mi_page_usable_block_size(page); |
72 | | for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page, block)) { |
73 | | mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t))); |
74 | | } |
75 | | } |
76 | | #endif |
77 | | return true; |
78 | | } |
79 | | |
80 | | static bool mi_page_is_valid_init(mi_page_t* page) { |
81 | | mi_assert_internal(mi_page_block_size(page) > 0); |
82 | | |
83 | | mi_assert_internal(page->used <= page->capacity); |
84 | | mi_assert_internal(page->capacity <= page->reserved); |
85 | | |
86 | | // [specbot P-NEW-1] block_size_shift must be consistent with block_size (L1, O(1)) |
87 | | mi_assert_internal(page->block_size_shift == 0 || (mi_page_block_size(page) == ((size_t)1 << page->block_size_shift))); |
88 | | // [specbot P-NEW-2] capacity must be nonzero when blocks are in use (L1, O(1)) |
89 | | mi_assert_internal(page->used == 0 || page->capacity > 0); |
90 | | // [specbot P-NEW-4] page_start must be non-null when capacity > 0 (L1, O(1)) |
91 | | mi_assert_internal(page->capacity == 0 || mi_page_start(page) != NULL); |
92 | | |
93 | | uint8_t* start = mi_page_start(page); |
94 | | mi_assert_internal(start == _mi_segment_page_start(_mi_page_segment(page), page, NULL)); |
95 | | mi_assert_internal(page->is_huge == (_mi_page_segment(page)->kind == MI_SEGMENT_HUGE)); |
96 | | //mi_assert_internal(start + page->capacity*page->block_size == page->top); |
97 | | |
98 | | mi_assert_internal(mi_page_list_is_valid(page,page->free)); |
99 | | mi_assert_internal(mi_page_list_is_valid(page,page->local_free)); |
100 | | |
101 | | // [specbot P-NEW-7] All free list blocks are aligned to block_size (L2, O(n)) |
102 | | { |
103 | | size_t bsize = mi_page_block_size(page); |
104 | | uint8_t* pstart = mi_page_start(page); |
105 | | for (mi_block_t* b = page->free; b != NULL; b = mi_block_next(page, b)) { |
106 | | mi_assert_internal(((uint8_t*)b - pstart) % bsize == 0); |
107 | | } |
108 | | for (mi_block_t* b = page->local_free; b != NULL; b = mi_block_next(page, b)) { |
109 | | mi_assert_internal(((uint8_t*)b - pstart) % bsize == 0); |
110 | | } |
111 | | } |
112 | | |
113 | | #if MI_DEBUG>3 // generally too expensive to check this |
114 | | if (page->free_is_zero) { |
115 | | const size_t ubsize = mi_page_usable_block_size(page); |
116 | | for(mi_block_t* block = page->free; block != NULL; block = mi_block_next(page,block)) { |
117 | | mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t))); |
118 | | } |
119 | | } |
120 | | #endif |
121 | | |
122 | | #if !MI_TRACK_ENABLED && !MI_TSAN |
123 | | mi_block_t* tfree = mi_page_thread_free(page); |
124 | | mi_assert_internal(mi_page_list_is_valid(page, tfree)); |
125 | | //size_t tfree_count = mi_page_list_count(page, tfree); |
126 | | //mi_assert_internal(tfree_count <= page->thread_freed + 1); |
127 | | #endif |
128 | | |
129 | | size_t free_count = mi_page_list_count(page, page->free) + mi_page_list_count(page, page->local_free); |
130 | | mi_assert_internal(page->used + free_count == page->capacity); |
131 | | |
132 | | return true; |
133 | | } |
134 | | |
135 | | extern mi_decl_hidden bool _mi_process_is_initialized; // has mi_process_init been called? |
136 | | |
137 | | bool _mi_page_is_valid(mi_page_t* page) { |
138 | | mi_assert_internal(mi_page_is_valid_init(page)); |
139 | | #if MI_SECURE |
140 | | mi_assert_internal(page->keys[0] != 0); |
141 | | #endif |
142 | | if (mi_page_heap(page)!=NULL) { |
143 | | mi_segment_t* segment = _mi_page_segment(page); |
144 | | |
145 | | // [specbot P-NEW-6] heap_tag must match owning heap's tag (L1, O(1)) |
146 | | mi_assert_internal(page->heap_tag == mi_page_heap(page)->tag); |
147 | | |
148 | | mi_assert_internal(!_mi_process_is_initialized || segment->thread_id==0 || segment->thread_id == mi_page_heap(page)->thread_id); |
149 | | #if MI_HUGE_PAGE_ABANDON |
150 | | if (segment->kind != MI_SEGMENT_HUGE) |
151 | | #endif |
152 | | { |
153 | | mi_page_queue_t* pq = mi_page_queue_of(page); |
154 | | mi_assert_internal(mi_page_queue_contains(pq, page)); |
155 | | mi_assert_internal(pq->block_size==mi_page_block_size(page) || mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_in_full(page)); |
156 | | mi_assert_internal(mi_heap_contains_queue(mi_page_heap(page),pq)); |
157 | | } |
158 | | } |
159 | | return true; |
160 | | } |
161 | | #endif |
162 | | |
163 | 0 | void _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) { |
164 | 0 | size_t ticks = 0; |
165 | 0 | while (!_mi_page_try_use_delayed_free(page, delay, override_never)) { |
166 | 0 | mi_atomic_yield_sleep(&ticks,10); |
167 | 0 | } |
168 | 0 | } |
169 | | |
170 | 0 | bool _mi_page_try_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) { |
171 | 0 | mi_thread_free_t tfreex; |
172 | 0 | mi_delayed_t old_delay; |
173 | 0 | mi_thread_free_t tfree; |
174 | 0 | size_t yield_count = 0; |
175 | 0 | do { |
176 | 0 | tfree = mi_atomic_load_acquire(&page->xthread_free); // note: must acquire as we can break/repeat this loop and not do a CAS; |
177 | 0 | tfreex = mi_tf_set_delayed(tfree, delay); |
178 | 0 | old_delay = mi_tf_delayed(tfree); |
179 | 0 | if mi_unlikely(old_delay == MI_DELAYED_FREEING) { |
180 | 0 | if (yield_count >= 4) return false; // give up after 4 tries |
181 | 0 | yield_count++; |
182 | 0 | mi_atomic_yield(); // delay until outstanding MI_DELAYED_FREEING are done. |
183 | | // tfree = mi_tf_set_delayed(tfree, MI_NO_DELAYED_FREE); // will cause CAS to busy fail |
184 | 0 | } |
185 | 0 | else if (delay == old_delay) { |
186 | 0 | break; // avoid atomic operation if already equal |
187 | 0 | } |
188 | 0 | else if (!override_never && old_delay == MI_NEVER_DELAYED_FREE) { |
189 | 0 | break; // leave never-delayed flag set |
190 | 0 | } |
191 | 0 | } while ((old_delay == MI_DELAYED_FREEING) || |
192 | 0 | !mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex)); |
193 | | |
194 | 0 | return true; // success |
195 | 0 | } |
196 | | |
197 | | /* ----------------------------------------------------------- |
198 | | Page collect the `local_free` and `thread_free` lists |
199 | | ----------------------------------------------------------- */ |
200 | | |
201 | | // Collect the local `thread_free` list using an atomic exchange. |
202 | | // Note: The exchange must be done atomically as this is used right after |
203 | | // moving to the full list in `mi_page_collect_ex` and we need to |
204 | | // ensure that there was no race where the page became unfull just before the move. |
205 | | static void _mi_page_thread_free_collect(mi_page_t* page) |
206 | 0 | { |
207 | 0 | mi_block_t* head; |
208 | 0 | mi_thread_free_t tfreex; |
209 | 0 | mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free); |
210 | 0 | do { |
211 | 0 | head = mi_tf_block(tfree); |
212 | 0 | tfreex = mi_tf_set_block(tfree,NULL); |
213 | 0 | } while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tfree, tfreex)); |
214 | | |
215 | | // return if the list is empty |
216 | 0 | if (head == NULL) return; |
217 | | |
218 | | // find the tail -- also to get a proper count (without data races) |
219 | 0 | size_t max_count = page->capacity; // cannot collect more than capacity |
220 | 0 | size_t count = 1; |
221 | 0 | mi_block_t* tail = head; |
222 | 0 | mi_block_t* next; |
223 | 0 | while ((next = mi_block_next(page,tail)) != NULL && count <= max_count) { |
224 | 0 | count++; |
225 | 0 | tail = next; |
226 | 0 | } |
227 | | // if `count > max_count` there was a memory corruption (possibly infinite list due to double multi-threaded free) |
228 | 0 | if mi_unlikely(count > max_count) { |
229 | 0 | _mi_error_message(EFAULT, "corrupted thread-free list (possibly due to a cross-thread double free)\n"); |
230 | 0 | return; // the thread-free items cannot be freed |
231 | 0 | } |
232 | | // if `count > page->used` there was another kind memory corruption (either in the page meta-data or in the linked list) |
233 | 0 | else if mi_unlikely(count > page->used) { |
234 | 0 | _mi_error_message(EFAULT, "corrupted meta-data in thread-free list\n"); |
235 | 0 | return; // the thread-free items cannot be freed |
236 | 0 | } |
237 | | |
238 | | // and append the current local free list |
239 | 0 | mi_block_set_next(page,tail, page->local_free); |
240 | 0 | page->local_free = head; |
241 | | |
242 | | // update counts now |
243 | 0 | mi_assert_internal(count <= UINT16_MAX); |
244 | 0 | mi_assert_internal(page->used >= (uint16_t)count); |
245 | 0 | page->used = page->used - (uint16_t)count; |
246 | |
|
247 | 0 | if (page->used == 0) { |
248 | | // CPython: the page may have had a QSBR goal set from a previous point when it |
249 | | // was all-free. That goal is no longer valid because the page was |
250 | | // allocated from and then freed again by other threads. |
251 | 0 | _PyMem_mi_page_clear_qsbr(page); |
252 | 0 | } |
253 | 0 | } |
254 | | |
255 | 0 | void _mi_page_free_collect(mi_page_t* page, bool force) { |
256 | 0 | mi_assert_internal(page!=NULL); |
257 | | |
258 | | // collect the thread free list |
259 | 0 | if (force || mi_page_thread_free(page) != NULL) { // quick test to avoid an atomic operation |
260 | 0 | _mi_page_thread_free_collect(page); |
261 | 0 | } |
262 | | |
263 | | // and the local free list |
264 | 0 | if (page->local_free != NULL) { |
265 | 0 | if mi_likely(page->free == NULL) { |
266 | | // usual case |
267 | 0 | page->free = page->local_free; |
268 | 0 | page->local_free = NULL; |
269 | 0 | page->free_is_zero = false; |
270 | 0 | } |
271 | 0 | else if (force) { |
272 | | // append -- only on shutdown (force) as this is a linear operation |
273 | 0 | mi_block_t* tail = page->local_free; |
274 | 0 | mi_block_t* next; |
275 | 0 | while ((next = mi_block_next(page, tail)) != NULL) { |
276 | 0 | tail = next; |
277 | 0 | } |
278 | 0 | mi_block_set_next(page, tail, page->free); |
279 | 0 | page->free = page->local_free; |
280 | 0 | page->local_free = NULL; |
281 | 0 | page->free_is_zero = false; |
282 | 0 | } |
283 | 0 | } |
284 | |
|
285 | 0 | mi_assert_internal(!force || page->local_free == NULL); |
286 | 0 | } |
287 | | |
288 | | |
289 | | |
290 | | /* ----------------------------------------------------------- |
291 | | Page fresh and retire |
292 | | ----------------------------------------------------------- */ |
293 | | |
294 | | // called from segments when reclaiming abandoned pages |
295 | 0 | void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) { |
296 | 0 | mi_assert_expensive(mi_page_is_valid_init(page)); |
297 | |
|
298 | 0 | mi_assert_internal(mi_page_heap(page) == heap); |
299 | 0 | mi_assert_internal(mi_page_thread_free_flag(page) != MI_NEVER_DELAYED_FREE); |
300 | | #if MI_HUGE_PAGE_ABANDON |
301 | | mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE); |
302 | | #endif |
303 | | |
304 | | // TODO: push on full queue immediately if it is full? |
305 | 0 | mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page)); |
306 | 0 | mi_page_queue_push(heap, pq, page); |
307 | 0 | _PyMem_mi_page_reclaimed(page); |
308 | 0 | mi_assert_expensive(_mi_page_is_valid(page)); |
309 | 0 | } |
310 | | |
311 | | // allocate a fresh page from a segment |
312 | 0 | static mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size_t block_size, size_t page_alignment) { |
313 | 0 | #if !MI_HUGE_PAGE_ABANDON |
314 | 0 | mi_assert_internal(pq != NULL); |
315 | 0 | mi_assert_internal(mi_heap_contains_queue(heap, pq)); |
316 | 0 | mi_assert_internal(page_alignment > 0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || block_size == pq->block_size); |
317 | 0 | #endif |
318 | 0 | mi_page_t* page = _mi_segment_page_alloc(heap, block_size, page_alignment, &heap->tld->segments); |
319 | 0 | if (page == NULL) { |
320 | | // this may be out-of-memory, or an abandoned page was reclaimed (and in our queue) |
321 | 0 | return NULL; |
322 | 0 | } |
323 | | #if MI_HUGE_PAGE_ABANDON |
324 | | mi_assert_internal(pq==NULL || _mi_page_segment(page)->page_kind != MI_PAGE_HUGE); |
325 | | #endif |
326 | 0 | mi_assert_internal(page_alignment >0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || _mi_page_segment(page)->kind != MI_SEGMENT_HUGE); |
327 | 0 | mi_assert_internal(pq!=NULL || mi_page_block_size(page) >= block_size); |
328 | | // a fresh page was found, initialize it |
329 | 0 | const size_t full_block_size = (pq == NULL || mi_page_is_huge(page) ? mi_page_block_size(page) : block_size); // see also: mi_segment_huge_page_alloc |
330 | 0 | mi_assert_internal(full_block_size >= block_size); |
331 | 0 | mi_page_init(heap, page, full_block_size, heap->tld); |
332 | 0 | _mi_stat_increase(&heap->tld->stats.pages, 1); |
333 | 0 | _mi_stat_increase(&heap->tld->stats.page_bins[_mi_page_stats_bin(page)], 1); |
334 | 0 | if (pq != NULL) { mi_page_queue_push(heap, pq, page); } |
335 | 0 | mi_assert_expensive(_mi_page_is_valid(page)); |
336 | 0 | return page; |
337 | 0 | } |
338 | | |
339 | | // Get a fresh page to use |
340 | 0 | static mi_page_t* mi_page_fresh(mi_heap_t* heap, mi_page_queue_t* pq) { |
341 | 0 | mi_assert_internal(mi_heap_contains_queue(heap, pq)); |
342 | 0 | mi_page_t* page = mi_page_fresh_alloc(heap, pq, pq->block_size, 0); |
343 | 0 | if (page==NULL) return NULL; |
344 | 0 | mi_assert_internal(pq->block_size==mi_page_block_size(page)); |
345 | 0 | mi_assert_internal(pq==mi_page_queue(heap, mi_page_block_size(page))); |
346 | 0 | return page; |
347 | 0 | } |
348 | | |
349 | | /* ----------------------------------------------------------- |
350 | | Do any delayed frees |
351 | | (put there by other threads if they deallocated in a full page) |
352 | | ----------------------------------------------------------- */ |
353 | 0 | void _mi_heap_delayed_free_all(mi_heap_t* heap) { |
354 | 0 | size_t ticks = 0; |
355 | 0 | while (!_mi_heap_delayed_free_partial(heap)) { |
356 | 0 | mi_atomic_yield_sleep(&ticks,10); |
357 | 0 | } |
358 | 0 | } |
359 | | |
360 | | // returns true if all delayed frees were processed |
361 | 0 | bool _mi_heap_delayed_free_partial(mi_heap_t* heap) { |
362 | | // take over the list (note: no atomic exchange since it is often NULL) |
363 | 0 | mi_block_t* block = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free); |
364 | 0 | while (block != NULL && !mi_atomic_cas_ptr_weak_acq_rel(mi_block_t, &heap->thread_delayed_free, &block, NULL)) { /* nothing */ }; |
365 | 0 | bool all_freed = true; |
366 | | |
367 | | // and free them all |
368 | 0 | while(block != NULL) { |
369 | 0 | mi_block_t* next = mi_block_nextx(heap,block, heap->keys); |
370 | | // use internal free instead of regular one to keep stats etc correct |
371 | 0 | if (!_mi_free_delayed_block(block)) { |
372 | | // we might already start delayed freeing while another thread has not yet |
373 | | // reset the delayed_freeing flag; in that case delay it further by reinserting the current block |
374 | | // into the delayed free list |
375 | 0 | all_freed = false; |
376 | 0 | mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free); |
377 | 0 | do { |
378 | 0 | mi_block_set_nextx(heap, block, dfree, heap->keys); |
379 | 0 | } while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block)); |
380 | 0 | } |
381 | 0 | block = next; |
382 | 0 | } |
383 | 0 | return all_freed; |
384 | 0 | } |
385 | | |
386 | | /* ----------------------------------------------------------- |
387 | | Unfull, abandon, free and retire |
388 | | ----------------------------------------------------------- */ |
389 | | |
390 | | // Move a page from the full list back to a regular list |
391 | 0 | void _mi_page_unfull(mi_page_t* page) { |
392 | 0 | mi_assert_internal(page != NULL); |
393 | 0 | mi_assert_expensive(_mi_page_is_valid(page)); |
394 | 0 | mi_assert_internal(mi_page_is_in_full(page)); |
395 | 0 | if (!mi_page_is_in_full(page)) return; |
396 | | |
397 | 0 | mi_heap_t* heap = mi_page_heap(page); |
398 | 0 | mi_page_queue_t* pqfull = &heap->pages[MI_BIN_FULL]; |
399 | 0 | mi_page_set_in_full(page, false); // to get the right queue |
400 | 0 | mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page); |
401 | 0 | mi_page_set_in_full(page, true); |
402 | 0 | mi_page_queue_enqueue_from_full(pq, pqfull, page); |
403 | 0 | } |
404 | | |
405 | 0 | static void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) { |
406 | 0 | mi_assert_internal(pq == mi_page_queue_of(page)); |
407 | 0 | mi_assert_internal(!mi_page_immediate_available(page)); |
408 | 0 | mi_assert_internal(!mi_page_is_in_full(page)); |
409 | |
|
410 | 0 | if (mi_page_is_in_full(page)) return; |
411 | 0 | mi_page_queue_enqueue_from(&mi_page_heap(page)->pages[MI_BIN_FULL], pq, page); |
412 | 0 | _mi_page_free_collect(page,false); // try to collect right away in case another thread freed just before MI_USE_DELAYED_FREE was set |
413 | 0 | } |
414 | | |
415 | | |
416 | | // Abandon a page with used blocks at the end of a thread. |
417 | | // Note: only call if it is ensured that no references exist from |
418 | | // the `page->heap->thread_delayed_free` into this page. |
419 | | // Currently only called through `mi_heap_collect_ex` which ensures this. |
420 | 0 | void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) { |
421 | 0 | mi_assert_internal(page != NULL); |
422 | 0 | mi_assert_expensive(_mi_page_is_valid(page)); |
423 | 0 | mi_assert_internal(pq == mi_page_queue_of(page)); |
424 | 0 | mi_assert_internal(mi_page_heap(page) != NULL); |
425 | |
|
426 | 0 | mi_heap_t* pheap = mi_page_heap(page); |
427 | |
|
428 | | #ifdef Py_GIL_DISABLED |
429 | | if (page->qsbr_node.next != NULL) { |
430 | | // CPython: remove from the QSBR queue, but keep the goal |
431 | | llist_remove(&page->qsbr_node); |
432 | | } |
433 | | #endif |
434 | | |
435 | | // remove from our page list |
436 | 0 | mi_segments_tld_t* segments_tld = &pheap->tld->segments; |
437 | 0 | mi_page_queue_remove(pq, page); |
438 | | |
439 | | // page is no longer associated with our heap |
440 | 0 | mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE); |
441 | 0 | mi_page_set_heap(page, NULL); |
442 | |
|
443 | | #if (MI_DEBUG>1) && !MI_TRACK_ENABLED |
444 | | // check there are no references left.. |
445 | | for (mi_block_t* block = (mi_block_t*)pheap->thread_delayed_free; block != NULL; block = mi_block_nextx(pheap, block, pheap->keys)) { |
446 | | mi_assert_internal(_mi_ptr_page(block) != page); |
447 | | } |
448 | | #endif |
449 | | |
450 | | // and abandon it |
451 | 0 | mi_assert_internal(mi_page_heap(page) == NULL); |
452 | 0 | _mi_segment_page_abandon(page,segments_tld); |
453 | 0 | } |
454 | | |
455 | | // force abandon a page |
456 | 0 | void _mi_page_force_abandon(mi_page_t* page) { |
457 | 0 | mi_heap_t* heap = mi_page_heap(page); |
458 | | // mark page as not using delayed free |
459 | 0 | _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false); |
460 | | |
461 | | // ensure this page is no longer in the heap delayed free list |
462 | 0 | _mi_heap_delayed_free_all(heap); |
463 | | // We can still access the page meta-info even if it is freed as we ensure |
464 | | // in `mi_segment_force_abandon` that the segment is not freed (yet) |
465 | 0 | if (page->capacity == 0) return; // it may have been freed now |
466 | | |
467 | | // and now unlink it from the page queue and abandon (or free) |
468 | 0 | mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page); |
469 | 0 | if (mi_page_all_free(page)) { |
470 | | // CPython: keep the page if it is still waiting for QSBR. |
471 | 0 | if (!_PyMem_mi_page_maybe_free(page, pq, false)) { |
472 | 0 | pq = mi_heap_page_queue_of(heap, page); |
473 | 0 | _mi_page_abandon(page, pq); |
474 | 0 | } |
475 | 0 | } |
476 | 0 | else { |
477 | 0 | _mi_page_abandon(page, pq); |
478 | 0 | } |
479 | 0 | } |
480 | | |
481 | | |
482 | | // Free a page with no more free blocks |
483 | 0 | void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) { |
484 | 0 | mi_assert_internal(page != NULL); |
485 | 0 | mi_assert_expensive(_mi_page_is_valid(page)); |
486 | 0 | mi_assert_internal(pq == mi_page_queue_of(page)); |
487 | 0 | mi_assert_internal(mi_page_all_free(page)); |
488 | 0 | mi_assert_internal(mi_page_thread_free_flag(page)!=MI_DELAYED_FREEING); |
489 | | |
490 | | // no more aligned blocks in here |
491 | 0 | mi_page_set_has_aligned(page, false); |
492 | |
|
493 | 0 | mi_heap_t* heap = mi_page_heap(page); |
494 | |
|
495 | | #ifdef Py_GIL_DISABLED |
496 | | mi_assert_internal(page->qsbr_goal == 0); |
497 | | mi_assert_internal(page->qsbr_node.next == NULL); |
498 | | #endif |
499 | | |
500 | | // remove from the page list |
501 | | // (no need to do _mi_heap_delayed_free first as all blocks are already free) |
502 | 0 | mi_segments_tld_t* segments_tld = &heap->tld->segments; |
503 | 0 | mi_page_queue_remove(pq, page); |
504 | | |
505 | | // and free it |
506 | 0 | mi_page_set_heap(page,NULL); |
507 | 0 | _mi_segment_page_free(page, force, segments_tld); |
508 | 0 | } |
509 | | |
510 | 0 | #define MI_RETIRE_CYCLES (16) |
511 | | |
512 | | // Retire a page with no more used blocks |
513 | | // Important to not retire too quickly though as new |
514 | | // allocations might coming. |
515 | | // Note: called from `mi_free` and benchmarks often |
516 | | // trigger this due to freeing everything and then |
517 | | // allocating again so careful when changing this. |
518 | 0 | void _mi_page_retire(mi_page_t* page) mi_attr_noexcept { |
519 | 0 | mi_assert_internal(page != NULL); |
520 | 0 | mi_assert_expensive(_mi_page_is_valid(page)); |
521 | 0 | mi_assert_internal(mi_page_all_free(page)); |
522 | |
|
523 | 0 | mi_page_set_has_aligned(page, false); |
524 | | |
525 | | // CPython: any previous QSBR goals are no longer valid because we reused the page |
526 | 0 | _PyMem_mi_page_clear_qsbr(page); |
527 | | |
528 | | // don't retire too often.. |
529 | | // (or we end up retiring and re-allocating most of the time) |
530 | | // NOTE: refine this more: we should not retire if this |
531 | | // is the only page left with free blocks. It is not clear |
532 | | // how to check this efficiently though... |
533 | | // for now, we don't retire if it is the only page left of this size class. |
534 | 0 | mi_page_queue_t* pq = mi_page_queue_of(page); |
535 | 0 | #if MI_RETIRE_CYCLES > 0 |
536 | 0 | const size_t bsize = mi_page_block_size(page); |
537 | 0 | if mi_likely( /* bsize < MI_MAX_RETIRE_SIZE && */ !mi_page_queue_is_special(pq)) { // not full or huge queue? |
538 | 0 | if (pq->last==page && pq->first==page) { // the only page in the queue? |
539 | 0 | mi_stat_counter_increase(_mi_stats_main.pages_retire,1); |
540 | 0 | page->retire_expire = (bsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4); |
541 | 0 | mi_heap_t* heap = mi_page_heap(page); |
542 | 0 | mi_assert_internal(pq >= heap->pages); |
543 | 0 | const size_t index = pq - heap->pages; |
544 | 0 | mi_assert_internal(index < MI_BIN_FULL && index < MI_BIN_HUGE); |
545 | 0 | if (index < heap->page_retired_min) heap->page_retired_min = index; |
546 | 0 | if (index > heap->page_retired_max) heap->page_retired_max = index; |
547 | 0 | mi_assert_internal(mi_page_all_free(page)); |
548 | 0 | return; // don't free after all |
549 | 0 | } |
550 | 0 | } |
551 | 0 | #endif |
552 | 0 | _PyMem_mi_page_maybe_free(page, pq, false); |
553 | 0 | } |
554 | | |
555 | | // free retired pages: we don't need to look at the entire queues |
556 | | // since we only retire pages that are at the head position in a queue. |
557 | 0 | void _mi_heap_collect_retired(mi_heap_t* heap, bool force) { |
558 | 0 | size_t min = MI_BIN_FULL; |
559 | 0 | size_t max = 0; |
560 | 0 | for(size_t bin = heap->page_retired_min; bin <= heap->page_retired_max; bin++) { |
561 | 0 | mi_page_queue_t* pq = &heap->pages[bin]; |
562 | 0 | mi_page_t* page = pq->first; |
563 | 0 | if (page != NULL && page->retire_expire != 0) { |
564 | 0 | if (mi_page_all_free(page)) { |
565 | 0 | page->retire_expire--; |
566 | 0 | if (force || page->retire_expire == 0) { |
567 | | #ifdef Py_GIL_DISABLED |
568 | | mi_assert_internal(page->qsbr_goal == 0); |
569 | | #endif |
570 | 0 | _PyMem_mi_page_maybe_free(page, pq, force); |
571 | 0 | } |
572 | 0 | else { |
573 | | // keep retired, update min/max |
574 | 0 | if (bin < min) min = bin; |
575 | 0 | if (bin > max) max = bin; |
576 | 0 | } |
577 | 0 | } |
578 | 0 | else { |
579 | 0 | page->retire_expire = 0; |
580 | 0 | } |
581 | 0 | } |
582 | 0 | } |
583 | 0 | heap->page_retired_min = min; |
584 | 0 | heap->page_retired_max = max; |
585 | 0 | } |
586 | | |
587 | | |
588 | | /* ----------------------------------------------------------- |
589 | | Initialize the initial free list in a page. |
590 | | In secure mode we initialize a randomized list by |
591 | | alternating between slices. |
592 | | ----------------------------------------------------------- */ |
593 | | |
594 | 0 | #define MI_MAX_SLICE_SHIFT (6) // at most 64 slices |
595 | | #define MI_MAX_SLICES (1UL << MI_MAX_SLICE_SHIFT) |
596 | 0 | #define MI_MIN_SLICES (2) |
597 | | |
598 | 0 | static void mi_page_free_list_extend_secure(mi_heap_t* const heap, mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) { |
599 | 0 | MI_UNUSED(stats); |
600 | 0 | #if (MI_SECURE < 2) |
601 | 0 | mi_assert_internal(page->free == NULL); |
602 | 0 | mi_assert_internal(page->local_free == NULL); |
603 | 0 | #endif |
604 | 0 | mi_assert_internal(page->capacity + extend <= page->reserved); |
605 | 0 | mi_assert_internal(bsize == mi_page_block_size(page)); |
606 | 0 | void* const page_area = mi_page_start(page); |
607 | | |
608 | | // initialize a randomized free list |
609 | | // set up `slice_count` slices to alternate between |
610 | 0 | size_t shift = MI_MAX_SLICE_SHIFT; |
611 | 0 | while ((extend >> shift) == 0) { |
612 | 0 | shift--; |
613 | 0 | } |
614 | 0 | const size_t slice_count = (size_t)1U << shift; |
615 | 0 | const size_t slice_extend = extend / slice_count; |
616 | 0 | mi_assert_internal(slice_extend >= 1); |
617 | 0 | mi_block_t* blocks[MI_MAX_SLICES]; // current start of the slice |
618 | 0 | size_t counts[MI_MAX_SLICES]; // available objects in the slice |
619 | 0 | for (size_t i = 0; i < slice_count; i++) { |
620 | 0 | blocks[i] = mi_page_block_at(page, page_area, bsize, page->capacity + i*slice_extend); |
621 | 0 | counts[i] = slice_extend; |
622 | 0 | } |
623 | 0 | counts[slice_count-1] += (extend % slice_count); // final slice holds the modulus too (todo: distribute evenly?) |
624 | | |
625 | | // and initialize the free list by randomly threading through them |
626 | | // set up first element |
627 | 0 | const uintptr_t r = _mi_heap_random_next(heap); |
628 | 0 | size_t current = r % slice_count; |
629 | 0 | counts[current]--; |
630 | 0 | mi_block_t* const free_start = blocks[current]; |
631 | | // and iterate through the rest; use `random_shuffle` for performance |
632 | 0 | uintptr_t rnd = _mi_random_shuffle(r|1); // ensure not 0 |
633 | 0 | for (size_t i = 1; i < extend; i++) { |
634 | | // call random_shuffle only every INTPTR_SIZE rounds |
635 | 0 | const size_t round = i%MI_INTPTR_SIZE; |
636 | 0 | if (round == 0) rnd = _mi_random_shuffle(rnd); |
637 | | // select a random next slice index |
638 | 0 | size_t next = ((rnd >> 8*round) & (slice_count-1)); |
639 | 0 | while (counts[next]==0) { // ensure it still has space |
640 | 0 | next++; |
641 | 0 | if (next==slice_count) next = 0; |
642 | 0 | } |
643 | | // and link the current block to it |
644 | 0 | counts[next]--; |
645 | 0 | mi_block_t* const block = blocks[current]; |
646 | 0 | blocks[current] = (mi_block_t*)((uint8_t*)block + bsize); // bump to the following block |
647 | 0 | mi_block_set_next(page, block, blocks[next]); // and set next; note: we may have `current == next` |
648 | 0 | current = next; |
649 | 0 | } |
650 | | // prepend to the free list (usually NULL) |
651 | 0 | mi_block_set_next(page, blocks[current], page->free); // end of the list |
652 | 0 | page->free = free_start; |
653 | 0 | } |
654 | | |
655 | | static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) |
656 | 0 | { |
657 | 0 | MI_UNUSED(stats); |
658 | 0 | #if (MI_SECURE < 2) |
659 | 0 | mi_assert_internal(page->free == NULL); |
660 | 0 | mi_assert_internal(page->local_free == NULL); |
661 | 0 | #endif |
662 | 0 | mi_assert_internal(page->capacity + extend <= page->reserved); |
663 | 0 | mi_assert_internal(bsize == mi_page_block_size(page)); |
664 | 0 | void* const page_area = mi_page_start(page); |
665 | |
|
666 | 0 | mi_block_t* const start = mi_page_block_at(page, page_area, bsize, page->capacity); |
667 | | |
668 | | // initialize a sequential free list |
669 | 0 | mi_block_t* const last = mi_page_block_at(page, page_area, bsize, page->capacity + extend - 1); |
670 | 0 | mi_block_t* block = start; |
671 | 0 | while(block <= last) { |
672 | 0 | mi_block_t* next = (mi_block_t*)((uint8_t*)block + bsize); |
673 | 0 | mi_block_set_next(page,block,next); |
674 | 0 | block = next; |
675 | 0 | } |
676 | | // prepend to free list (usually `NULL`) |
677 | 0 | mi_block_set_next(page, last, page->free); |
678 | 0 | page->free = start; |
679 | 0 | } |
680 | | |
681 | | /* ----------------------------------------------------------- |
682 | | Page initialize and extend the capacity |
683 | | ----------------------------------------------------------- */ |
684 | | |
685 | 0 | #define MI_MAX_EXTEND_SIZE (4*1024) // heuristic, one OS page seems to work well. |
686 | | #if (MI_SECURE>=2) |
687 | | #define MI_MIN_EXTEND (8*MI_SECURE) // extend at least by this many blocks |
688 | | #else |
689 | 0 | #define MI_MIN_EXTEND (4) |
690 | | #endif |
691 | | |
692 | | // Extend the capacity (up to reserved) by initializing a free list |
693 | | // We do at most `MI_MAX_EXTEND` to avoid touching too much memory |
694 | | // Note: we also experimented with "bump" allocation on the first |
695 | | // allocations but this did not speed up any benchmark (due to an |
696 | | // extra test in malloc? or cache effects?) |
697 | 0 | static bool mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) { |
698 | 0 | mi_assert_expensive(mi_page_is_valid_init(page)); |
699 | 0 | #if (MI_SECURE < 2) |
700 | 0 | mi_assert(page->free == NULL); |
701 | 0 | mi_assert(page->local_free == NULL); |
702 | 0 | if (page->free != NULL) return true; |
703 | 0 | #endif |
704 | 0 | if (page->capacity >= page->reserved) return true; |
705 | | |
706 | 0 | mi_stat_counter_increase(tld->stats.pages_extended, 1); |
707 | | |
708 | | // calculate the extend count |
709 | 0 | const size_t bsize = mi_page_block_size(page); |
710 | 0 | size_t extend = page->reserved - page->capacity; |
711 | 0 | mi_assert_internal(extend > 0); |
712 | |
|
713 | 0 | size_t max_extend = (bsize >= MI_MAX_EXTEND_SIZE ? MI_MIN_EXTEND : MI_MAX_EXTEND_SIZE/bsize); |
714 | 0 | if (max_extend < MI_MIN_EXTEND) { max_extend = MI_MIN_EXTEND; } |
715 | 0 | mi_assert_internal(max_extend > 0); |
716 | |
|
717 | 0 | if (extend > max_extend) { |
718 | | // ensure we don't touch memory beyond the page to reduce page commit. |
719 | | // the `lean` benchmark tests this. Going from 1 to 8 increases rss by 50%. |
720 | 0 | extend = max_extend; |
721 | 0 | } |
722 | |
|
723 | 0 | mi_assert_internal(extend > 0 && extend + page->capacity <= page->reserved); |
724 | 0 | mi_assert_internal(extend < (1UL<<16)); |
725 | | |
726 | | // and append the extend the free list |
727 | 0 | if (extend < MI_MIN_SLICES || MI_SECURE<2) { //!mi_option_is_enabled(mi_option_secure)) { |
728 | 0 | mi_page_free_list_extend(page, bsize, extend, &tld->stats ); |
729 | 0 | } |
730 | 0 | else { |
731 | 0 | mi_page_free_list_extend_secure(heap, page, bsize, extend, &tld->stats); |
732 | 0 | } |
733 | | // enable the new free list |
734 | 0 | page->capacity += (uint16_t)extend; |
735 | 0 | _mi_stat_increase(&tld->stats.page_committed, extend * bsize); |
736 | 0 | mi_assert_expensive(mi_page_is_valid_init(page)); |
737 | 0 | return true; |
738 | 0 | } |
739 | | |
740 | | // Initialize a fresh page |
741 | 0 | static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi_tld_t* tld) { |
742 | 0 | mi_assert(page != NULL); |
743 | 0 | mi_segment_t* segment = _mi_page_segment(page); |
744 | 0 | mi_assert(segment != NULL); |
745 | 0 | mi_assert_internal(block_size > 0); |
746 | | // set fields |
747 | 0 | mi_page_set_heap(page, heap); |
748 | 0 | page->use_qsbr = heap->page_use_qsbr; // CPython |
749 | 0 | page->debug_offset = heap->debug_offset; // CPython |
750 | 0 | page->block_size = block_size; |
751 | 0 | size_t page_size; |
752 | 0 | page->page_start = _mi_segment_page_start(segment, page, &page_size); |
753 | 0 | mi_track_mem_noaccess(page->page_start,page_size); |
754 | 0 | mi_assert_internal(mi_page_block_size(page) <= page_size); |
755 | 0 | mi_assert_internal(page_size <= page->slice_count*MI_SEGMENT_SLICE_SIZE); |
756 | 0 | mi_assert_internal(page_size / block_size < (1L<<16)); |
757 | 0 | page->reserved = (uint16_t)(page_size / block_size); |
758 | 0 | mi_assert_internal(page->reserved > 0); |
759 | | #if (MI_PADDING || MI_ENCODE_FREELIST) |
760 | | page->keys[0] = _mi_heap_random_next(heap); |
761 | | page->keys[1] = _mi_heap_random_next(heap); |
762 | | #endif |
763 | 0 | page->free_is_zero = page->is_zero_init; |
764 | | #if MI_DEBUG>2 |
765 | | if (page->is_zero_init) { |
766 | | mi_track_mem_defined(page->page_start, page_size); |
767 | | mi_assert_expensive(mi_mem_is_zero(page->page_start, page_size)); |
768 | | } |
769 | | #endif |
770 | 0 | mi_assert_internal(page->is_committed); |
771 | 0 | if (block_size > 0 && _mi_is_power_of_two(block_size)) { |
772 | 0 | page->block_size_shift = (uint8_t)(mi_ctz((uintptr_t)block_size)); |
773 | 0 | } |
774 | 0 | else { |
775 | 0 | page->block_size_shift = 0; |
776 | 0 | } |
777 | |
|
778 | 0 | mi_assert_internal(page->capacity == 0); |
779 | 0 | mi_assert_internal(page->free == NULL); |
780 | 0 | mi_assert_internal(page->used == 0); |
781 | 0 | mi_assert_internal(page->xthread_free == 0); |
782 | 0 | mi_assert_internal(page->next == NULL); |
783 | 0 | mi_assert_internal(page->prev == NULL); |
784 | | #ifdef Py_GIL_DISABLED |
785 | | mi_assert_internal(page->qsbr_goal == 0); |
786 | | mi_assert_internal(page->qsbr_node.next == NULL); |
787 | | #endif |
788 | 0 | mi_assert_internal(page->retire_expire == 0); |
789 | 0 | mi_assert_internal(!mi_page_has_aligned(page)); |
790 | | #if (MI_PADDING || MI_ENCODE_FREELIST) |
791 | | mi_assert_internal(page->keys[0] != 0); |
792 | | mi_assert_internal(page->keys[1] != 0); |
793 | | #endif |
794 | 0 | mi_assert_internal(page->block_size_shift == 0 || (block_size == ((size_t)1 << page->block_size_shift))); |
795 | 0 | mi_assert_expensive(mi_page_is_valid_init(page)); |
796 | | |
797 | | // initialize an initial free list |
798 | 0 | if (mi_page_extend_free(heap,page,tld)) { |
799 | 0 | mi_assert(mi_page_immediate_available(page)); |
800 | 0 | } |
801 | 0 | return; |
802 | 0 | } |
803 | | |
804 | | |
805 | | /* ----------------------------------------------------------- |
806 | | Find pages with free blocks |
807 | | -------------------------------------------------------------*/ |
808 | | |
809 | | // search for a best next page to use for at most N pages (often cut short if immediate blocks are available) |
810 | 0 | #define MI_MAX_CANDIDATE_SEARCH (4) |
811 | | |
812 | | // is the page not yet used up to its reserved space? |
813 | 0 | static bool mi_page_is_expandable(const mi_page_t* page) { |
814 | 0 | mi_assert_internal(page != NULL); |
815 | 0 | mi_assert_internal(page->capacity <= page->reserved); |
816 | 0 | return (page->capacity < page->reserved); |
817 | 0 | } |
818 | | |
819 | | |
820 | | // Find a page with free blocks of `page->block_size`. |
821 | | static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* pq, bool first_try) |
822 | 0 | { |
823 | | // search through the pages in "next fit" order |
824 | | #if MI_STAT |
825 | | size_t count = 0; |
826 | | #endif |
827 | 0 | size_t candidate_count = 0; // we reset this on the first candidate to limit the search |
828 | 0 | mi_page_t* page_candidate = NULL; // a page with free space |
829 | 0 | mi_page_t* page = pq->first; |
830 | |
|
831 | 0 | while (page != NULL) |
832 | 0 | { |
833 | 0 | mi_page_t* next = page->next; // remember next |
834 | | #if MI_STAT |
835 | | count++; |
836 | | #endif |
837 | 0 | candidate_count++; |
838 | | |
839 | | // collect freed blocks by us and other threads |
840 | 0 | _mi_page_free_collect(page, false); |
841 | |
|
842 | 0 | #if MI_MAX_CANDIDATE_SEARCH > 1 |
843 | | // search up to N pages for a best candidate |
844 | | |
845 | | // is the local free list non-empty? |
846 | 0 | const bool immediate_available = mi_page_immediate_available(page); |
847 | | |
848 | | // if the page is completely full, move it to the `mi_pages_full` |
849 | | // queue so we don't visit long-lived pages too often. |
850 | 0 | if (!immediate_available && !mi_page_is_expandable(page)) { |
851 | 0 | mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page)); |
852 | 0 | mi_page_to_full(page, pq); |
853 | 0 | } |
854 | 0 | else { |
855 | | // the page has free space, make it a candidate |
856 | | // we prefer non-expandable pages with high usage as candidates (to reduce commit, and increase chances of free-ing up pages) |
857 | 0 | if (page_candidate == NULL) { |
858 | 0 | page_candidate = page; |
859 | 0 | candidate_count = 0; |
860 | 0 | } |
861 | | // prefer to reuse fuller pages (in the hope the less used page gets freed) |
862 | 0 | else if (page->used >= page_candidate->used && !mi_page_is_mostly_used(page) && !mi_page_is_expandable(page)) { |
863 | 0 | page_candidate = page; |
864 | 0 | } |
865 | | // if we find a non-expandable candidate, or searched for N pages, return with the best candidate |
866 | 0 | if (immediate_available || candidate_count > MI_MAX_CANDIDATE_SEARCH) { |
867 | 0 | mi_assert_internal(page_candidate!=NULL); |
868 | 0 | break; |
869 | 0 | } |
870 | 0 | } |
871 | | #else |
872 | | // first-fit algorithm |
873 | | // If the page contains free blocks, we are done |
874 | | if (mi_page_immediate_available(page) || mi_page_is_expandable(page)) { |
875 | | break; // pick this one |
876 | | } |
877 | | |
878 | | // If the page is completely full, move it to the `mi_pages_full` |
879 | | // queue so we don't visit long-lived pages too often. |
880 | | mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page)); |
881 | | mi_page_to_full(page, pq); |
882 | | #endif |
883 | | |
884 | 0 | page = next; |
885 | 0 | } // for each page |
886 | |
|
887 | 0 | mi_heap_stat_counter_increase(heap, page_searches, count); |
888 | 0 | mi_heap_stat_counter_increase(heap, page_searches_count, 1); |
889 | | |
890 | | // set the page to the best candidate |
891 | 0 | if (page_candidate != NULL) { |
892 | 0 | page = page_candidate; |
893 | 0 | } |
894 | 0 | if (page != NULL) { |
895 | 0 | if (!mi_page_immediate_available(page)) { |
896 | 0 | mi_assert_internal(mi_page_is_expandable(page)); |
897 | 0 | if (!mi_page_extend_free(heap, page, heap->tld)) { |
898 | 0 | page = NULL; // failed to extend |
899 | 0 | } |
900 | 0 | } |
901 | 0 | mi_assert_internal(page == NULL || mi_page_immediate_available(page)); |
902 | 0 | } |
903 | |
|
904 | 0 | if (page == NULL) { |
905 | 0 | _PyMem_mi_heap_collect_qsbr(heap); // CPython: some pages might be safe to free now |
906 | 0 | _mi_heap_collect_retired(heap, false); // perhaps make a page available? |
907 | 0 | page = mi_page_fresh(heap, pq); |
908 | 0 | if (page == NULL && first_try) { |
909 | | // out-of-memory _or_ an abandoned page with free blocks was reclaimed, try once again |
910 | 0 | page = mi_page_queue_find_free_ex(heap, pq, false); |
911 | 0 | } |
912 | 0 | } |
913 | 0 | else { |
914 | | // move the page to the front of the queue |
915 | 0 | mi_page_queue_move_to_front(heap, pq, page); |
916 | 0 | page->retire_expire = 0; |
917 | 0 | _PyMem_mi_page_clear_qsbr(page); |
918 | | // _mi_heap_collect_retired(heap, false); // update retire counts; note: increases rss on MemoryLoad bench so don't do this |
919 | 0 | } |
920 | 0 | mi_assert_internal(page == NULL || mi_page_immediate_available(page)); |
921 | | |
922 | |
|
923 | 0 | return page; |
924 | 0 | } |
925 | | |
926 | | |
927 | | |
928 | | // Find a page with free blocks of `size`. |
929 | 0 | static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) { |
930 | 0 | mi_page_queue_t* pq = mi_page_queue(heap, size); |
931 | | |
932 | | // check the first page: we even do this with candidate search or otherwise we re-search every time |
933 | 0 | mi_page_t* page = pq->first; |
934 | 0 | if (page != NULL) { |
935 | | #if (MI_SECURE>=2) // in secure mode, we extend half the time to increase randomness |
936 | | if (page->capacity < page->reserved && ((_mi_heap_random_next(heap) & 1) == 1)) { |
937 | | mi_page_extend_free(heap, page, heap->tld); |
938 | | mi_assert_internal(mi_page_immediate_available(page)); |
939 | | } |
940 | | else |
941 | | #endif |
942 | 0 | { |
943 | 0 | _mi_page_free_collect(page,false); |
944 | 0 | } |
945 | |
|
946 | 0 | if (mi_page_immediate_available(page)) { |
947 | 0 | page->retire_expire = 0; |
948 | 0 | _PyMem_mi_page_clear_qsbr(page); |
949 | 0 | return page; // fast path |
950 | 0 | } |
951 | 0 | } |
952 | | |
953 | 0 | return mi_page_queue_find_free_ex(heap, pq, true); |
954 | 0 | } |
955 | | |
956 | | |
957 | | /* ----------------------------------------------------------- |
958 | | Users can register a deferred free function called |
959 | | when the `free` list is empty. Since the `local_free` |
960 | | is separate this is deterministically called after |
961 | | a certain number of allocations. |
962 | | ----------------------------------------------------------- */ |
963 | | |
964 | | // The program should only install a single deferred free handler before doing allocation. |
965 | | static _Atomic(void*) deferred_free; // is `mi_deferred_free_fun*` (but some platforms don't support atomic function pointers) |
966 | | static _Atomic(void*) deferred_arg; |
967 | | |
968 | 0 | void _mi_deferred_free(mi_heap_t* heap, bool force) { |
969 | 0 | heap->tld->heartbeat++; |
970 | 0 | mi_deferred_free_fun* const fun = (mi_deferred_free_fun*)mi_atomic_load_ptr_acquire(void,&deferred_free); |
971 | 0 | if (fun != NULL && !heap->tld->recurse) { |
972 | 0 | heap->tld->recurse = true; |
973 | 0 | void* const arg = mi_atomic_load_ptr_acquire(void,&deferred_arg); |
974 | 0 | fun(force, heap->tld->heartbeat, arg); |
975 | 0 | heap->tld->recurse = false; |
976 | 0 | } |
977 | 0 | } |
978 | | |
979 | 0 | void mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noexcept { |
980 | 0 | mi_atomic_store_ptr_release(void,&deferred_arg, arg); |
981 | 0 | mi_atomic_store_ptr_release(void,&deferred_free, (void*)fn); |
982 | 0 | } |
983 | | |
984 | | |
985 | | /* ----------------------------------------------------------- |
986 | | General allocation |
987 | | ----------------------------------------------------------- */ |
988 | | |
989 | | // Large and huge page allocation. |
990 | | // Huge pages contain just one block, and the segment contains just that page (as `MI_SEGMENT_HUGE`). |
991 | | // Huge pages are also use if the requested alignment is very large (> MI_BLOCK_ALIGNMENT_MAX) |
992 | | // so their size is not always `> MI_LARGE_OBJ_SIZE_MAX`. |
993 | 0 | static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t page_alignment) { |
994 | 0 | size_t block_size = _mi_os_good_alloc_size(size); |
995 | 0 | mi_assert_internal(mi_bin(block_size) == MI_BIN_HUGE || page_alignment > 0); |
996 | 0 | bool is_huge = (block_size > MI_LARGE_OBJ_SIZE_MAX || page_alignment > 0); |
997 | | #if MI_HUGE_PAGE_ABANDON |
998 | | mi_page_queue_t* pq = (is_huge ? NULL : mi_page_queue(heap, block_size)); |
999 | | #else |
1000 | 0 | mi_page_queue_t* pq = mi_page_queue(heap, is_huge ? MI_LARGE_OBJ_SIZE_MAX+1 : block_size); |
1001 | 0 | mi_assert_internal(!is_huge || mi_page_queue_is_huge(pq)); |
1002 | 0 | #endif |
1003 | 0 | mi_page_t* page = mi_page_fresh_alloc(heap, pq, block_size, page_alignment); |
1004 | 0 | if (page != NULL) { |
1005 | 0 | mi_assert_internal(mi_page_immediate_available(page)); |
1006 | |
|
1007 | 0 | if (is_huge) { |
1008 | 0 | mi_assert_internal(mi_page_is_huge(page)); |
1009 | 0 | mi_assert_internal(_mi_page_segment(page)->kind == MI_SEGMENT_HUGE); |
1010 | 0 | mi_assert_internal(_mi_page_segment(page)->used==1); |
1011 | | #if MI_HUGE_PAGE_ABANDON |
1012 | | mi_assert_internal(_mi_page_segment(page)->thread_id==0); // abandoned, not in the huge queue |
1013 | | mi_page_set_heap(page, NULL); |
1014 | | #endif |
1015 | 0 | } |
1016 | 0 | else { |
1017 | 0 | mi_assert_internal(!mi_page_is_huge(page)); |
1018 | 0 | } |
1019 | |
|
1020 | 0 | const size_t bsize = mi_page_usable_block_size(page); // note: not `mi_page_block_size` to account for padding |
1021 | | /*if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { |
1022 | | mi_heap_stat_increase(heap, malloc_large, bsize); |
1023 | | mi_heap_stat_counter_increase(heap, malloc_large_count, 1); |
1024 | | } |
1025 | | else */ |
1026 | 0 | { |
1027 | 0 | _mi_stat_increase(&heap->tld->stats.malloc_huge, bsize); |
1028 | 0 | _mi_stat_counter_increase(&heap->tld->stats.malloc_huge_count, 1); |
1029 | 0 | } |
1030 | 0 | } |
1031 | 0 | return page; |
1032 | 0 | } |
1033 | | |
1034 | | |
1035 | | // Allocate a page |
1036 | | // Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed. |
1037 | 0 | static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignment) mi_attr_noexcept { |
1038 | | // huge allocation? |
1039 | 0 | const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size` |
1040 | 0 | if mi_unlikely(req_size > (MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) || huge_alignment > 0) { |
1041 | 0 | if mi_unlikely(req_size > MI_MAX_ALLOC_SIZE) { |
1042 | 0 | _mi_error_message(EOVERFLOW, "allocation request is too large (%zu bytes)\n", req_size); |
1043 | 0 | return NULL; |
1044 | 0 | } |
1045 | 0 | else { |
1046 | 0 | _PyMem_mi_heap_collect_qsbr(heap); // CPython |
1047 | 0 | return mi_large_huge_page_alloc(heap,size,huge_alignment); |
1048 | 0 | } |
1049 | 0 | } |
1050 | 0 | else { |
1051 | | // otherwise find a page with free blocks in our size segregated queues |
1052 | | #if MI_PADDING |
1053 | | mi_assert_internal(size >= MI_PADDING_SIZE); |
1054 | | #endif |
1055 | 0 | return mi_find_free_page(heap, size); |
1056 | 0 | } |
1057 | 0 | } |
1058 | | |
1059 | | // Generic allocation routine if the fast path (`alloc.c:mi_page_malloc`) does not succeed. |
1060 | | // Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed. |
1061 | | // The `huge_alignment` is normally 0 but is set to a multiple of MI_SLICE_SIZE for |
1062 | | // very large requested alignments in which case we use a huge singleton page. |
1063 | | void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, mi_page_t** ppage) mi_attr_noexcept |
1064 | 0 | { |
1065 | 0 | mi_assert_internal(heap != NULL); |
1066 | | |
1067 | | // initialize if necessary |
1068 | 0 | if mi_unlikely(!mi_heap_is_initialized(heap)) { |
1069 | 0 | heap = mi_heap_get_default(); // calls mi_thread_init |
1070 | 0 | if mi_unlikely(!mi_heap_is_initialized(heap)) { return NULL; } |
1071 | 0 | } |
1072 | 0 | mi_assert_internal(mi_heap_is_initialized(heap)); |
1073 | | |
1074 | | // do administrative tasks every N generic mallocs |
1075 | 0 | if mi_unlikely(++heap->generic_count >= 100) { |
1076 | 0 | heap->generic_collect_count += heap->generic_count; |
1077 | 0 | heap->generic_count = 0; |
1078 | | // call potential deferred free routines |
1079 | 0 | _mi_deferred_free(heap, false); |
1080 | | |
1081 | | // free delayed frees from other threads (but skip contended ones) |
1082 | 0 | _mi_heap_delayed_free_partial(heap); |
1083 | | |
1084 | | // collect every once in a while (10000 by default) |
1085 | 0 | const long generic_collect = mi_option_get_clamp(mi_option_generic_collect, 1, 1000000L); |
1086 | 0 | if (heap->generic_collect_count >= generic_collect) { |
1087 | 0 | heap->generic_collect_count = 0; |
1088 | 0 | mi_heap_collect(heap, false /* force? */); |
1089 | 0 | } |
1090 | 0 | } |
1091 | | |
1092 | | // find (or allocate) a page of the right size |
1093 | 0 | mi_page_t* page = mi_find_page(heap, size, huge_alignment); |
1094 | 0 | if mi_unlikely(page == NULL) { // first time out of memory, try to collect and retry the allocation once more |
1095 | 0 | mi_heap_collect(heap, true /* force */); |
1096 | 0 | page = mi_find_page(heap, size, huge_alignment); |
1097 | 0 | } |
1098 | |
|
1099 | 0 | if mi_unlikely(page == NULL) { // out of memory |
1100 | 0 | const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size` |
1101 | 0 | _mi_error_message(ENOMEM, "unable to allocate memory (%zu bytes)\n", req_size); |
1102 | 0 | return NULL; |
1103 | 0 | } |
1104 | | |
1105 | 0 | mi_assert_internal(mi_page_immediate_available(page)); |
1106 | 0 | mi_assert_internal(mi_page_block_size(page) >= size); |
1107 | | |
1108 | | // and try again, this time succeeding! (i.e. this should never recurse through _mi_page_malloc) |
1109 | 0 | void* const p = _mi_page_malloc_zero(heap, page, size, zero, ppage); |
1110 | 0 | mi_assert_internal(p != NULL); |
1111 | | |
1112 | | // move singleton pages to the full queue |
1113 | 0 | if (page->reserved == page->used) { |
1114 | 0 | mi_page_to_full(page, mi_page_queue_of(page)); |
1115 | 0 | } |
1116 | 0 | return p; |
1117 | 0 | } |