PostgreSQL Source Code git master
Loading...
Searching...
No Matches
slab.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * slab.c
4 * SLAB allocator definitions.
5 *
6 * SLAB is a MemoryContext implementation designed for cases where large
7 * numbers of equally-sized objects can be allocated and freed efficiently
8 * with minimal memory wastage and fragmentation.
9 *
10 *
11 * Portions Copyright (c) 2017-2026, PostgreSQL Global Development Group
12 *
13 * IDENTIFICATION
14 * src/backend/utils/mmgr/slab.c
15 *
16 *
17 * NOTE:
18 * The constant allocation size allows significant simplification and various
19 * optimizations over more general purpose allocators. The blocks are carved
20 * into chunks of exactly the right size, wasting only the space required to
21 * MAXALIGN the allocated chunks.
22 *
23 * Slab can also help reduce memory fragmentation in cases where longer-lived
24 * chunks remain stored on blocks while most of the other chunks have already
25 * been pfree'd. We give priority to putting new allocations into the
26 * "fullest" block. This help avoid having too many sparsely used blocks
27 * around and allows blocks to more easily become completely unused which
28 * allows them to be eventually free'd.
29 *
30 * We identify the "fullest" block to put new allocations on by using a block
31 * from the lowest populated element of the context's "blocklist" array.
32 * This is an array of dlists containing blocks which we partition by the
33 * number of free chunks which block has. Blocks with fewer free chunks are
34 * stored in a lower indexed dlist array slot. Full blocks go on the 0th
35 * element of the blocklist array. So that we don't have to have too many
36 * elements in the array, each dlist in the array is responsible for a range
37 * of free chunks. When a chunk is palloc'd or pfree'd we may need to move
38 * the block onto another dlist if the number of free chunks crosses the
39 * range boundary that the current list is responsible for. Having just a
40 * few blocklist elements reduces the number of times we must move the block
41 * onto another dlist element.
42 *
43 * We keep track of free chunks within each block by using a block-level free
44 * list. We consult this list when we allocate a new chunk in the block.
45 * The free list is a linked list, the head of which is pointed to with
46 * SlabBlock's freehead field. Each subsequent list item is stored in the
47 * free chunk's memory. We ensure chunks are large enough to store this
48 * address.
49 *
50 * When we allocate a new block, technically all chunks are free, however, to
51 * avoid having to write out the entire block to set the linked list for the
52 * free chunks for every chunk in the block, we instead store a pointer to
53 * the next "unused" chunk on the block and keep track of how many of these
54 * unused chunks there are. When a new block is malloc'd, all chunks are
55 * unused. The unused pointer starts with the first chunk on the block and
56 * as chunks are allocated, the unused pointer is incremented. As chunks are
57 * pfree'd, the unused pointer never goes backwards. The unused pointer can
58 * be thought of as a high watermark for the maximum number of chunks in the
59 * block which have been in use concurrently. When a chunk is pfree'd the
60 * chunk is put onto the head of the free list and the unused pointer is not
61 * changed. We only consume more unused chunks if we run out of free chunks
62 * on the free list. This method effectively gives priority to using
63 * previously used chunks over previously unused chunks, which should perform
64 * better due to CPU caching effects.
65 *
66 *-------------------------------------------------------------------------
67 */
68
69#include "postgres.h"
70
71#include "lib/ilist.h"
72#include "utils/memdebug.h"
73#include "utils/memutils.h"
76
77#define Slab_BLOCKHDRSZ MAXALIGN(sizeof(SlabBlock))
78
79#ifdef MEMORY_CONTEXT_CHECKING
80/*
81 * Size of the memory required to store the SlabContext.
82 * MEMORY_CONTEXT_CHECKING builds need some extra memory for the isChunkFree
83 * array.
84 */
85#define Slab_CONTEXT_HDRSZ(chunksPerBlock) \
86 (sizeof(SlabContext) + ((chunksPerBlock) * sizeof(bool)))
87#else
88#define Slab_CONTEXT_HDRSZ(chunksPerBlock) sizeof(SlabContext)
89#endif
90
91/*
92 * The number of partitions to divide the blocklist into based their number of
93 * free chunks. There must be at least 2.
94 */
95#define SLAB_BLOCKLIST_COUNT 3
96
97/* The maximum number of completely empty blocks to keep around for reuse. */
98#define SLAB_MAXIMUM_EMPTY_BLOCKS 10
99
100/*
101 * SlabContext is a specialized implementation of MemoryContext.
102 */
103typedef struct SlabContext
104{
105 MemoryContextData header; /* Standard memory-context fields */
106 /* Allocation parameters for this context: */
107 uint32 chunkSize; /* the requested (non-aligned) chunk size */
108 uint32 fullChunkSize; /* chunk size with chunk header and alignment */
109 uint32 blockSize; /* the size to make each block of chunks */
110 int32 chunksPerBlock; /* number of chunks that fit in 1 block */
111 int32 curBlocklistIndex; /* index into the blocklist[] element
112 * containing the fullest, blocks */
113#ifdef MEMORY_CONTEXT_CHECKING
114 bool *isChunkFree; /* array to mark free chunks in a block during
115 * SlabCheck */
116#endif
117
118 int32 blocklist_shift; /* number of bits to shift the nfree count
119 * by to get the index into blocklist[] */
120 dclist_head emptyblocks; /* empty blocks to use up first instead of
121 * mallocing new blocks */
122
123 /*
124 * Blocks with free space, grouped by the number of free chunks they
125 * contain. Completely full blocks are stored in the 0th element.
126 * Completely empty blocks are stored in emptyblocks or free'd if we have
127 * enough empty blocks already.
128 */
131
132/*
133 * SlabBlock
134 * Structure of a single slab block.
135 *
136 * slab: pointer back to the owning MemoryContext
137 * nfree: number of chunks on the block which are unallocated
138 * nunused: number of chunks on the block unallocated and not on the block's
139 * freelist.
140 * freehead: linked-list header storing a pointer to the first free chunk on
141 * the block. Subsequent pointers are stored in the chunk's memory. NULL
142 * indicates the end of the list.
143 * unused: pointer to the next chunk which has yet to be used.
144 * node: doubly-linked list node for the context's blocklist
145 */
146typedef struct SlabBlock
147{
148 SlabContext *slab; /* owning context */
149 int32 nfree; /* number of chunks on free + unused chunks */
150 int32 nunused; /* number of unused chunks */
151 MemoryChunk *freehead; /* pointer to the first free chunk */
152 MemoryChunk *unused; /* pointer to the next unused chunk */
153 dlist_node node; /* doubly-linked list for blocklist[] */
155
156
157#define Slab_CHUNKHDRSZ sizeof(MemoryChunk)
158#define SlabChunkGetPointer(chk) \
159 ((void *) (((char *) (chk)) + sizeof(MemoryChunk)))
160
161/*
162 * SlabBlockGetChunk
163 * Obtain a pointer to the nth (0-based) chunk in the block
164 */
165#define SlabBlockGetChunk(slab, block, n) \
166 ((MemoryChunk *) ((char *) (block) + Slab_BLOCKHDRSZ \
167 + ((n) * (slab)->fullChunkSize)))
168
169#if defined(MEMORY_CONTEXT_CHECKING) || defined(USE_ASSERT_CHECKING)
170
171/*
172 * SlabChunkIndex
173 * Get the 0-based index of how many chunks into the block the given
174 * chunk is.
175*/
176#define SlabChunkIndex(slab, block, chunk) \
177 (((char *) (chunk) - (char *) SlabBlockGetChunk(slab, block, 0)) / \
178 (slab)->fullChunkSize)
179
180/*
181 * SlabChunkMod
182 * A MemoryChunk should always be at an address which is a multiple of
183 * fullChunkSize starting from the 0th chunk position. This will return
184 * non-zero if it's not.
185 */
186#define SlabChunkMod(slab, block, chunk) \
187 (((char *) (chunk) - (char *) SlabBlockGetChunk(slab, block, 0)) % \
188 (slab)->fullChunkSize)
189
190#endif
191
192/*
193 * SlabIsValid
194 * True iff set is a valid slab allocation set.
195 */
196#define SlabIsValid(set) ((set) && IsA(set, SlabContext))
197
198/*
199 * SlabBlockIsValid
200 * True iff block is a valid block of slab allocation set.
201 */
202#define SlabBlockIsValid(block) \
203 ((block) && SlabIsValid((block)->slab))
204
205/*
206 * SlabBlocklistIndex
207 * Determine the blocklist index that a block should be in for the given
208 * number of free chunks.
209 */
210static inline int32
212{
213 int32 index;
214 int32 blocklist_shift = slab->blocklist_shift;
215
216 Assert(nfree >= 0 && nfree <= slab->chunksPerBlock);
217
218 /*
219 * Determine the blocklist index based on the number of free chunks. We
220 * must ensure that 0 free chunks is dedicated to index 0. Everything
221 * else must be >= 1 and < SLAB_BLOCKLIST_COUNT.
222 *
223 * To make this as efficient as possible, we exploit some two's complement
224 * arithmetic where we reverse the sign before bit shifting. This results
225 * in an nfree of 0 using index 0 and anything non-zero staying non-zero.
226 * This is exploiting 0 and -0 being the same in two's complement. When
227 * we're done, we just need to flip the sign back over again for a
228 * positive index.
229 */
230 index = -((-nfree) >> blocklist_shift);
231
232 if (nfree == 0)
233 Assert(index == 0);
234 else
236
237 return index;
238}
239
240/*
241 * SlabFindNextBlockListIndex
242 * Search blocklist for blocks which have free chunks and return the
243 * index of the blocklist found containing at least 1 block with free
244 * chunks. If no block can be found we return 0.
245 *
246 * Note: We give priority to fuller blocks so that these are filled before
247 * emptier blocks. This is done to increase the chances that mostly-empty
248 * blocks will eventually become completely empty so they can be free'd.
249 */
250static int32
252{
253 /* start at 1 as blocklist[0] is for full blocks. */
254 for (int i = 1; i < SLAB_BLOCKLIST_COUNT; i++)
255 {
256 /* return the first found non-empty index */
257 if (!dlist_is_empty(&slab->blocklist[i]))
258 return i;
259 }
260
261 /* no blocks with free space */
262 return 0;
263}
264
265/*
266 * SlabGetNextFreeChunk
267 * Return the next free chunk in block and update the block to account
268 * for the returned chunk now being used.
269 */
270static inline MemoryChunk *
272{
273 MemoryChunk *chunk;
274
275 Assert(block->nfree > 0);
276
277 if (block->freehead != NULL)
278 {
279 chunk = block->freehead;
280
281 /*
282 * Pop the chunk from the linked list of free chunks. The pointer to
283 * the next free chunk is stored in the chunk itself.
284 */
286 block->freehead = *(MemoryChunk **) SlabChunkGetPointer(chunk);
287
288 /* check nothing stomped on the free chunk's memory */
289 Assert(block->freehead == NULL ||
290 (block->freehead >= SlabBlockGetChunk(slab, block, 0) &&
291 block->freehead <= SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1) &&
292 SlabChunkMod(slab, block, block->freehead) == 0));
293 }
294 else
295 {
296 Assert(block->nunused > 0);
297
298 chunk = block->unused;
299 block->unused = (MemoryChunk *) (((char *) block->unused) + slab->fullChunkSize);
300 block->nunused--;
301 }
302
303 block->nfree--;
304
305 return chunk;
306}
307
308/*
309 * SlabContextCreate
310 * Create a new Slab context.
311 *
312 * parent: parent context, or NULL if top-level context
313 * name: name of context (must be statically allocated)
314 * blockSize: allocation block size
315 * chunkSize: allocation chunk size
316 *
317 * The Slab_CHUNKHDRSZ + MAXALIGN(chunkSize + 1) may not exceed
318 * MEMORYCHUNK_MAX_VALUE.
319 * 'blockSize' may not exceed MEMORYCHUNK_MAX_BLOCKOFFSET.
320 */
323 const char *name,
324 Size blockSize,
325 Size chunkSize)
326{
327 int chunksPerBlock;
328 Size fullChunkSize;
329 SlabContext *slab;
330 int i;
331
332 /* ensure MemoryChunk's size is properly maxaligned */
334 "sizeof(MemoryChunk) is not maxaligned");
336
337 /*
338 * Ensure there's enough space to store the pointer to the next free chunk
339 * in the memory of the (otherwise) unused allocation.
340 */
341 if (chunkSize < sizeof(MemoryChunk *))
342 chunkSize = sizeof(MemoryChunk *);
343
344 /* length of the maxaligned chunk including the chunk header */
345#ifdef MEMORY_CONTEXT_CHECKING
346 /* ensure there's always space for the sentinel byte */
347 fullChunkSize = Slab_CHUNKHDRSZ + MAXALIGN(chunkSize + 1);
348#else
349 fullChunkSize = Slab_CHUNKHDRSZ + MAXALIGN(chunkSize);
350#endif
351
352 Assert(fullChunkSize <= MEMORYCHUNK_MAX_VALUE);
353
354 /* compute the number of chunks that will fit on each block */
355 chunksPerBlock = (blockSize - Slab_BLOCKHDRSZ) / fullChunkSize;
356
357 /* Make sure the block can store at least one chunk. */
358 if (chunksPerBlock == 0)
359 elog(ERROR, "block size %zu for slab is too small for %zu-byte chunks",
360 blockSize, chunkSize);
361
362
363
364 slab = (SlabContext *) malloc(Slab_CONTEXT_HDRSZ(chunksPerBlock));
365 if (slab == NULL)
366 {
370 errmsg("out of memory"),
371 errdetail("Failed while creating memory context \"%s\".",
372 name)));
373 }
374
375 /*
376 * Avoid writing code that can fail between here and MemoryContextCreate;
377 * we'd leak the header if we ereport in this stretch.
378 */
379
380 /* See comments about Valgrind interactions in aset.c */
381 VALGRIND_CREATE_MEMPOOL(slab, 0, false);
382 /* This vchunk covers the SlabContext only */
383 VALGRIND_MEMPOOL_ALLOC(slab, slab, sizeof(SlabContext));
384
385 /* Fill in SlabContext-specific header fields */
386 slab->chunkSize = (uint32) chunkSize;
387 slab->fullChunkSize = (uint32) fullChunkSize;
388 slab->blockSize = (uint32) blockSize;
389 slab->chunksPerBlock = chunksPerBlock;
390 slab->curBlocklistIndex = 0;
391
392 /*
393 * Compute a shift that guarantees that shifting chunksPerBlock with it is
394 * < SLAB_BLOCKLIST_COUNT - 1. The reason that we subtract 1 from
395 * SLAB_BLOCKLIST_COUNT in this calculation is that we reserve the 0th
396 * blocklist element for blocks which have no free chunks.
397 *
398 * We calculate the number of bits to shift by rather than a divisor to
399 * divide by as performing division each time we need to find the
400 * blocklist index would be much slower.
401 */
402 slab->blocklist_shift = 0;
403 while ((slab->chunksPerBlock >> slab->blocklist_shift) >= (SLAB_BLOCKLIST_COUNT - 1))
404 slab->blocklist_shift++;
405
406 /* initialize the list to store empty blocks to be reused */
407 dclist_init(&slab->emptyblocks);
408
409 /* initialize each blocklist slot */
410 for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
411 dlist_init(&slab->blocklist[i]);
412
413#ifdef MEMORY_CONTEXT_CHECKING
414 /* set the isChunkFree pointer right after the end of the context */
415 slab->isChunkFree = (bool *) ((char *) slab + sizeof(SlabContext));
416#endif
417
418 /* Finally, do the type-independent part of context creation */
422 parent,
423 name);
424
425 return (MemoryContext) slab;
426}
427
428/*
429 * SlabReset
430 * Frees all memory which is allocated in the given set.
431 *
432 * The code simply frees all the blocks in the context - we don't keep any
433 * keeper blocks or anything like that.
434 */
435void
437{
438 SlabContext *slab = (SlabContext *) context;
440 int i;
441
442 Assert(SlabIsValid(slab));
443
444#ifdef MEMORY_CONTEXT_CHECKING
445 /* Check for corruption and leaks before freeing */
446 SlabCheck(context);
447#endif
448
449 /* release any retained empty blocks */
451 {
452 SlabBlock *block = dlist_container(SlabBlock, node, miter.cur);
453
455
456#ifdef CLOBBER_FREED_MEMORY
457 wipe_mem(block, slab->blockSize);
458#endif
459
460 /* As in aset.c, free block-header vchunks explicitly */
461 VALGRIND_MEMPOOL_FREE(slab, block);
462
463 free(block);
464 context->mem_allocated -= slab->blockSize;
465 }
466
467 /* walk over blocklist and free the blocks */
468 for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
469 {
471 {
472 SlabBlock *block = dlist_container(SlabBlock, node, miter.cur);
473
474 dlist_delete(miter.cur);
475
476#ifdef CLOBBER_FREED_MEMORY
477 wipe_mem(block, slab->blockSize);
478#endif
479
480 /* As in aset.c, free block-header vchunks explicitly */
481 VALGRIND_MEMPOOL_FREE(slab, block);
482
483 free(block);
484 context->mem_allocated -= slab->blockSize;
485 }
486 }
487
488 /*
489 * Instruct Valgrind to throw away all the vchunks associated with this
490 * context, except for the one covering the SlabContext. This gets rid of
491 * the vchunks for whatever user data is getting discarded by the context
492 * reset.
493 */
494 VALGRIND_MEMPOOL_TRIM(slab, slab, sizeof(SlabContext));
495
496 slab->curBlocklistIndex = 0;
497
498 Assert(context->mem_allocated == 0);
499}
500
501/*
502 * SlabDelete
503 * Free all memory which is allocated in the given context.
504 */
505void
507{
508 /* Reset to release all the SlabBlocks */
509 SlabReset(context);
510
511 /* Destroy the vpool -- see notes in aset.c */
513
514 /* And free the context header */
515 free(context);
516}
517
518/*
519 * Small helper for allocating a new chunk from a chunk, to avoid duplicating
520 * the code between SlabAlloc() and SlabAllocFromNewBlock().
521 */
522static inline void *
524 MemoryChunk *chunk, Size size)
525{
526 SlabContext *slab = (SlabContext *) context;
527
528 /*
529 * Check that the chunk pointer is actually somewhere on the block and is
530 * aligned as expected.
531 */
532 Assert(chunk >= SlabBlockGetChunk(slab, block, 0));
533 Assert(chunk <= SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1));
534 Assert(SlabChunkMod(slab, block, chunk) == 0);
535
536 /* Prepare to initialize the chunk header. */
538
540
541#ifdef MEMORY_CONTEXT_CHECKING
542 chunk->requested_size = size;
543 /* slab mark to catch clobber of "unused" space */
547 slab->chunkSize,
548 slab->fullChunkSize -
549 (slab->chunkSize + Slab_CHUNKHDRSZ));
550#endif
551
552#ifdef RANDOMIZE_ALLOCATED_MEMORY
553 /* fill the allocated space with junk */
554 randomize_mem((char *) MemoryChunkGetPointer(chunk), size);
555#endif
556
557 /* Disallow access to the chunk header. */
559
560 return MemoryChunkGetPointer(chunk);
561}
562
564static void *
565SlabAllocFromNewBlock(MemoryContext context, Size size, int flags)
566{
567 SlabContext *slab = (SlabContext *) context;
568 SlabBlock *block;
569 MemoryChunk *chunk;
570 dlist_head *blocklist;
571 int blocklist_idx;
572
573 /* to save allocating a new one, first check the empty blocks list */
574 if (dclist_count(&slab->emptyblocks) > 0)
575 {
577
578 block = dlist_container(SlabBlock, node, node);
579
580 /*
581 * SlabFree() should have left this block in a valid state with all
582 * chunks free. Ensure that's the case.
583 */
584 Assert(block->nfree == slab->chunksPerBlock);
585
586 /* fetch the next chunk from this block */
587 chunk = SlabGetNextFreeChunk(slab, block);
588 }
589 else
590 {
591 block = (SlabBlock *) malloc(slab->blockSize);
592
593 if (unlikely(block == NULL))
594 return MemoryContextAllocationFailure(context, size, flags);
595
596 /* Make a vchunk covering the new block's header */
598
599 block->slab = slab;
600 context->mem_allocated += slab->blockSize;
601
602 /* use the first chunk in the new block */
603 chunk = SlabBlockGetChunk(slab, block, 0);
604
605 block->nfree = slab->chunksPerBlock - 1;
606 block->unused = SlabBlockGetChunk(slab, block, 1);
607 block->freehead = NULL;
608 block->nunused = slab->chunksPerBlock - 1;
609 }
610
611 /* find the blocklist element for storing blocks with 1 used chunk */
612 blocklist_idx = SlabBlocklistIndex(slab, block->nfree);
613 blocklist = &slab->blocklist[blocklist_idx];
614
615 /* this better be empty. We just added a block thinking it was */
616 Assert(dlist_is_empty(blocklist));
617
618 dlist_push_head(blocklist, &block->node);
619
621
622 return SlabAllocSetupNewChunk(context, block, chunk, size);
623}
624
625/*
626 * SlabAllocInvalidSize
627 * Handle raising an ERROR for an invalid size request. We don't do this
628 * in slab alloc as calling the elog functions would force the compiler
629 * to setup the stack frame in SlabAlloc. For performance reasons, we
630 * want to avoid that.
631 */
634static void
636{
637 SlabContext *slab = (SlabContext *) context;
638
639 elog(ERROR, "unexpected alloc chunk size %zu (expected %u)", size,
640 slab->chunkSize);
641}
642
643/*
644 * SlabAlloc
645 * Returns a pointer to a newly allocated memory chunk or raises an ERROR
646 * on allocation failure, or returns NULL when flags contains
647 * MCXT_ALLOC_NO_OOM. 'size' must be the same size as was specified
648 * during SlabContextCreate().
649 *
650 * This function should only contain the most common code paths. Everything
651 * else should be in pg_noinline helper functions, thus avoiding the overhead
652 * of creating a stack frame for the common cases. Allocating memory is often
653 * a bottleneck in many workloads, so avoiding stack frame setup is
654 * worthwhile. Helper functions should always directly return the newly
655 * allocated memory so that we can just return that address directly as a tail
656 * call.
657 */
658void *
659SlabAlloc(MemoryContext context, Size size, int flags)
660{
661 SlabContext *slab = (SlabContext *) context;
662 SlabBlock *block;
663 MemoryChunk *chunk;
664
665 Assert(SlabIsValid(slab));
666
667 /* sanity check that this is pointing to a valid blocklist */
668 Assert(slab->curBlocklistIndex >= 0);
670
671 /*
672 * Make sure we only allow correct request size. This doubles as the
673 * MemoryContextCheckSize check.
674 */
675 if (unlikely(size != slab->chunkSize))
676 SlabAllocInvalidSize(context, size);
677
678 if (unlikely(slab->curBlocklistIndex == 0))
679 {
680 /*
681 * Handle the case when there are no partially filled blocks
682 * available. This happens either when the last allocation took the
683 * last chunk in the block, or when SlabFree() free'd the final block.
684 */
685 return SlabAllocFromNewBlock(context, size, flags);
686 }
687 else
688 {
689 dlist_head *blocklist = &slab->blocklist[slab->curBlocklistIndex];
691
692 Assert(!dlist_is_empty(blocklist));
693
694 /* grab the block from the blocklist */
695 block = dlist_head_element(SlabBlock, node, blocklist);
696
697 /* make sure we actually got a valid block, with matching nfree */
698 Assert(block != NULL);
699 Assert(slab->curBlocklistIndex == SlabBlocklistIndex(slab, block->nfree));
700 Assert(block->nfree > 0);
701
702 /* fetch the next chunk from this block */
703 chunk = SlabGetNextFreeChunk(slab, block);
704
705 /* get the new blocklist index based on the new free chunk count */
707
708 /*
709 * Handle the case where the blocklist index changes. This also deals
710 * with blocks becoming full as only full blocks go at index 0.
711 */
713 {
714 dlist_delete_from(blocklist, &block->node);
716
717 if (dlist_is_empty(blocklist))
719 }
720 }
721
722 return SlabAllocSetupNewChunk(context, block, chunk, size);
723}
724
725/*
726 * SlabFree
727 * Frees allocated memory; memory is removed from the slab.
728 */
729void
730SlabFree(void *pointer)
731{
732 MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
733 SlabBlock *block;
734 SlabContext *slab;
735 int curBlocklistIdx;
736 int newBlocklistIdx;
737
738 /* Allow access to the chunk header. */
740
741 block = MemoryChunkGetBlock(chunk);
742
743 /*
744 * For speed reasons we just Assert that the referenced block is good.
745 * Future field experience may show that this Assert had better become a
746 * regular runtime test-and-elog check.
747 */
748 Assert(SlabBlockIsValid(block));
749 slab = block->slab;
750
751#ifdef MEMORY_CONTEXT_CHECKING
752 /* See comments in AllocSetFree about uses of ERROR and WARNING here */
753 /* Test for previously-freed chunk */
754 if (unlikely(chunk->requested_size == InvalidAllocSize))
755 elog(ERROR, "detected double pfree in %s %p",
756 slab->header.name, chunk);
757 /* Test for someone scribbling on unused space in chunk */
759 if (!sentinel_ok(pointer, slab->chunkSize))
760 elog(WARNING, "detected write past chunk end in %s %p",
761 slab->header.name, chunk);
762 /* Reset requested_size to InvalidAllocSize in free chunks */
763 chunk->requested_size = InvalidAllocSize;
764#endif
765
766 /* push this chunk onto the head of the block's free list */
767 *(MemoryChunk **) pointer = block->freehead;
768 block->freehead = chunk;
769
770 block->nfree++;
771
772 Assert(block->nfree > 0);
773 Assert(block->nfree <= slab->chunksPerBlock);
774
775#ifdef CLOBBER_FREED_MEMORY
776 /* don't wipe the free list MemoryChunk pointer stored in the chunk */
777 wipe_mem((char *) pointer + sizeof(MemoryChunk *),
778 slab->chunkSize - sizeof(MemoryChunk *));
779#endif
780
781 curBlocklistIdx = SlabBlocklistIndex(slab, block->nfree - 1);
783
784 /*
785 * Check if the block needs to be moved to another element on the
786 * blocklist based on it now having 1 more free chunk.
787 */
789 {
790 /* do the move */
793
794 /*
795 * The blocklist[curBlocklistIdx] may now be empty or we may now be
796 * able to use a lower-element blocklist. We'll need to redetermine
797 * what the slab->curBlocklistIndex is if the current blocklist was
798 * changed or if a lower element one was changed. We must ensure we
799 * use the list with the fullest block(s).
800 */
802 {
804
805 /*
806 * We know there must be a block with at least 1 unused chunk as
807 * we just pfree'd one. Ensure curBlocklistIndex reflects this.
808 */
809 Assert(slab->curBlocklistIndex > 0);
810 }
811 }
812
813 /* Handle when a block becomes completely empty */
814 if (unlikely(block->nfree == slab->chunksPerBlock))
815 {
816 /* remove the block */
818
819 /*
820 * To avoid thrashing malloc/free, we keep a list of empty blocks that
821 * we can reuse again instead of having to malloc a new one.
822 */
824 dclist_push_head(&slab->emptyblocks, &block->node);
825 else
826 {
827 /*
828 * When we have enough empty blocks stored already, we actually
829 * free the block.
830 */
831#ifdef CLOBBER_FREED_MEMORY
832 wipe_mem(block, slab->blockSize);
833#endif
834
835 /* As in aset.c, free block-header vchunks explicitly */
836 VALGRIND_MEMPOOL_FREE(slab, block);
837
838 free(block);
839 slab->header.mem_allocated -= slab->blockSize;
840 }
841
842 /*
843 * Check if we need to reset the blocklist index. This is required
844 * when the blocklist this block is on has become completely empty.
845 */
846 if (slab->curBlocklistIndex == newBlocklistIdx &&
849 }
850}
851
852/*
853 * SlabRealloc
854 * Change the allocated size of a chunk.
855 *
856 * As Slab is designed for allocating equally-sized chunks of memory, it can't
857 * do an actual chunk size change. We try to be gentle and allow calls with
858 * exactly the same size, as in that case we can simply return the same
859 * chunk. When the size differs, we throw an error.
860 *
861 * We could also allow requests with size < chunkSize. That however seems
862 * rather pointless - Slab is meant for chunks of constant size, and moreover
863 * realloc is usually used to enlarge the chunk.
864 */
865void *
866SlabRealloc(void *pointer, Size size, int flags)
867{
868 MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
869 SlabBlock *block;
870 SlabContext *slab;
871
872 /* Allow access to the chunk header. */
874
875 block = MemoryChunkGetBlock(chunk);
876
877 /* Disallow access to the chunk header. */
879
880 /*
881 * Try to verify that we have a sane block pointer: the block header
882 * should reference a slab context. (We use a test-and-elog, not just
883 * Assert, because it seems highly likely that we're here in error in the
884 * first place.)
885 */
886 if (!SlabBlockIsValid(block))
887 elog(ERROR, "could not find block containing chunk %p", chunk);
888 slab = block->slab;
889
890 /* can't do actual realloc with slab, but let's try to be gentle */
891 if (size == slab->chunkSize)
892 return pointer;
893
894 elog(ERROR, "slab allocator does not support realloc()");
895 return NULL; /* keep compiler quiet */
896}
897
898/*
899 * SlabGetChunkContext
900 * Return the MemoryContext that 'pointer' belongs to.
901 */
904{
905 MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
906 SlabBlock *block;
907
908 /* Allow access to the chunk header. */
910
911 block = MemoryChunkGetBlock(chunk);
912
913 /* Disallow access to the chunk header. */
915
916 Assert(SlabBlockIsValid(block));
917
918 return &block->slab->header;
919}
920
921/*
922 * SlabGetChunkSpace
923 * Given a currently-allocated chunk, determine the total space
924 * it occupies (including all memory-allocation overhead).
925 */
926Size
927SlabGetChunkSpace(void *pointer)
928{
929 MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
930 SlabBlock *block;
931 SlabContext *slab;
932
933 /* Allow access to the chunk header. */
935
936 block = MemoryChunkGetBlock(chunk);
937
938 /* Disallow access to the chunk header. */
940
941 Assert(SlabBlockIsValid(block));
942 slab = block->slab;
943
944 return slab->fullChunkSize;
945}
946
947/*
948 * SlabIsEmpty
949 * Is the slab empty of any allocated space?
950 */
951bool
953{
954 Assert(SlabIsValid((SlabContext *) context));
955
956 return (context->mem_allocated == 0);
957}
958
959/*
960 * SlabStats
961 * Compute stats about memory consumption of a Slab context.
962 *
963 * printfunc: if not NULL, pass a human-readable stats string to this.
964 * passthru: pass this pointer through to printfunc.
965 * totals: if not NULL, add stats about this context into *totals.
966 * print_to_stderr: print stats to stderr if true, elog otherwise.
967 */
968void
972 bool print_to_stderr)
973{
974 SlabContext *slab = (SlabContext *) context;
975 Size nblocks = 0;
976 Size freechunks = 0;
977 Size totalspace;
978 Size freespace = 0;
979 int i;
980
981 Assert(SlabIsValid(slab));
982
983 /* Include context header in totalspace */
984 totalspace = Slab_CONTEXT_HDRSZ(slab->chunksPerBlock);
985
986 /* Add the space consumed by blocks in the emptyblocks list */
987 totalspace += dclist_count(&slab->emptyblocks) * slab->blockSize;
988
989 for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
990 {
991 dlist_iter iter;
992
993 dlist_foreach(iter, &slab->blocklist[i])
994 {
995 SlabBlock *block = dlist_container(SlabBlock, node, iter.cur);
996
997 nblocks++;
998 totalspace += slab->blockSize;
999 freespace += slab->fullChunkSize * block->nfree;
1000 freechunks += block->nfree;
1001 }
1002 }
1003
1004 if (printfunc)
1005 {
1006 char stats_string[200];
1007
1008 /* XXX should we include free chunks on empty blocks? */
1010 "%zu total in %zu blocks; %u empty blocks; %zu free (%zu chunks); %zu used",
1011 totalspace, nblocks, dclist_count(&slab->emptyblocks),
1012 freespace, freechunks, totalspace - freespace);
1014 }
1015
1016 if (totals)
1017 {
1018 totals->nblocks += nblocks;
1019 totals->freechunks += freechunks;
1020 totals->totalspace += totalspace;
1021 totals->freespace += freespace;
1022 }
1023}
1024
1025
1026#ifdef MEMORY_CONTEXT_CHECKING
1027
1028/*
1029 * SlabCheck
1030 * Walk through all blocks looking for inconsistencies.
1031 *
1032 * NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll
1033 * find yourself in an infinite loop when trouble occurs, because this
1034 * routine will be entered again when elog cleanup tries to release memory!
1035 */
1036void
1037SlabCheck(MemoryContext context)
1038{
1039 SlabContext *slab = (SlabContext *) context;
1040 int i;
1041 int nblocks = 0;
1042 const char *name = slab->header.name;
1043 dlist_iter iter;
1044
1045 Assert(SlabIsValid(slab));
1046 Assert(slab->chunksPerBlock > 0);
1047
1048 /*
1049 * Have a look at the empty blocks. These should have all their chunks
1050 * marked as free. Ensure that's the case.
1051 */
1052 dclist_foreach(iter, &slab->emptyblocks)
1053 {
1054 SlabBlock *block = dlist_container(SlabBlock, node, iter.cur);
1055
1056 if (block->nfree != slab->chunksPerBlock)
1057 elog(WARNING, "problem in slab %s: empty block %p should have %d free chunks but has %d chunks free",
1058 name, block, slab->chunksPerBlock, block->nfree);
1059 }
1060
1061 /* walk the non-empty block lists */
1062 for (i = 0; i < SLAB_BLOCKLIST_COUNT; i++)
1063 {
1064 int j,
1065 nfree;
1066
1067 /* walk all blocks on this blocklist */
1068 dlist_foreach(iter, &slab->blocklist[i])
1069 {
1070 SlabBlock *block = dlist_container(SlabBlock, node, iter.cur);
1072
1073 /*
1074 * Make sure the number of free chunks (in the block header)
1075 * matches the position in the blocklist.
1076 */
1077 if (SlabBlocklistIndex(slab, block->nfree) != i)
1078 elog(WARNING, "problem in slab %s: block %p is on blocklist %d but should be on blocklist %d",
1079 name, block, i, SlabBlocklistIndex(slab, block->nfree));
1080
1081 /* make sure the block is not empty */
1082 if (block->nfree >= slab->chunksPerBlock)
1083 elog(WARNING, "problem in slab %s: empty block %p incorrectly stored on blocklist element %d",
1084 name, block, i);
1085
1086 /* make sure the slab pointer correctly points to this context */
1087 if (block->slab != slab)
1088 elog(WARNING, "problem in slab %s: bogus slab link in block %p",
1089 name, block);
1090
1091 /* reset the array of free chunks for this block */
1092 memset(slab->isChunkFree, 0, (slab->chunksPerBlock * sizeof(bool)));
1093 nfree = 0;
1094
1095 /* walk through the block's free list chunks */
1096 cur_chunk = block->freehead;
1097 while (cur_chunk != NULL)
1098 {
1099 int chunkidx = SlabChunkIndex(slab, block, cur_chunk);
1100
1101 /*
1102 * Ensure the free list link points to something on the block
1103 * at an address aligned according to the full chunk size.
1104 */
1105 if (cur_chunk < SlabBlockGetChunk(slab, block, 0) ||
1106 cur_chunk > SlabBlockGetChunk(slab, block, slab->chunksPerBlock - 1) ||
1107 SlabChunkMod(slab, block, cur_chunk) != 0)
1108 elog(WARNING, "problem in slab %s: bogus free list link %p in block %p",
1109 name, cur_chunk, block);
1110
1111 /* count the chunk and mark it free on the free chunk array */
1112 nfree++;
1113 slab->isChunkFree[chunkidx] = true;
1114
1115 /* read pointer of the next free chunk */
1118 }
1119
1120 /* check that the unused pointer matches what nunused claims */
1121 if (SlabBlockGetChunk(slab, block, slab->chunksPerBlock - block->nunused) !=
1122 block->unused)
1123 elog(WARNING, "problem in slab %s: mismatch detected between nunused chunks and unused pointer in block %p",
1124 name, block);
1125
1126 /*
1127 * count the remaining free chunks that have yet to make it onto
1128 * the block's free list.
1129 */
1130 cur_chunk = block->unused;
1131 for (j = 0; j < block->nunused; j++)
1132 {
1133 int chunkidx = SlabChunkIndex(slab, block, cur_chunk);
1134
1135
1136 /* count the chunk as free and mark it as so in the array */
1137 nfree++;
1138 if (chunkidx < slab->chunksPerBlock)
1139 slab->isChunkFree[chunkidx] = true;
1140
1141 /* move forward 1 chunk */
1142 cur_chunk = (MemoryChunk *) (((char *) cur_chunk) + slab->fullChunkSize);
1143 }
1144
1145 for (j = 0; j < slab->chunksPerBlock; j++)
1146 {
1147 if (!slab->isChunkFree[j])
1148 {
1149 MemoryChunk *chunk = SlabBlockGetChunk(slab, block, j);
1151
1152 /* Allow access to the chunk header. */
1154
1156
1157 /* Disallow access to the chunk header. */
1159
1160 /*
1161 * check the chunk's blockoffset correctly points back to
1162 * the block
1163 */
1164 if (chunkblock != block)
1165 elog(WARNING, "problem in slab %s: bogus block link in block %p, chunk %p",
1166 name, block, chunk);
1167
1168 /* check the sentinel byte is intact */
1169 Assert(slab->chunkSize < (slab->fullChunkSize - Slab_CHUNKHDRSZ));
1170 if (!sentinel_ok(chunk, Slab_CHUNKHDRSZ + slab->chunkSize))
1171 elog(WARNING, "problem in slab %s: detected write past chunk end in block %p, chunk %p",
1172 name, block, chunk);
1173 }
1174 }
1175
1176 /*
1177 * Make sure we got the expected number of free chunks (as tracked
1178 * in the block header).
1179 */
1180 if (nfree != block->nfree)
1181 elog(WARNING, "problem in slab %s: nfree in block %p is %d but %d chunk were found as free",
1182 name, block, block->nfree, nfree);
1183
1184 nblocks++;
1185 }
1186 }
1187
1188 /* the stored empty blocks are tracked in mem_allocated too */
1189 nblocks += dclist_count(&slab->emptyblocks);
1190
1191 Assert(nblocks * slab->blockSize == context->mem_allocated);
1192}
1193
1194#endif /* MEMORY_CONTEXT_CHECKING */
#define pg_noinline
Definition c.h:321
#define MAXALIGN(LEN)
Definition c.h:896
#define pg_noreturn
Definition c.h:190
#define Assert(condition)
Definition c.h:943
int32_t int32
Definition c.h:620
#define unlikely(x)
Definition c.h:438
uint32_t uint32
Definition c.h:624
#define StaticAssertDecl(condition, errmessage)
Definition c.h:1008
size_t Size
Definition c.h:689
int errcode(int sqlerrcode)
Definition elog.c:874
int errdetail(const char *fmt,...) pg_attribute_printf(1
#define WARNING
Definition elog.h:37
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
#define ereport(elevel,...)
Definition elog.h:152
#define dlist_foreach(iter, lhead)
Definition ilist.h:623
static void dlist_init(dlist_head *head)
Definition ilist.h:314
static void dlist_delete_from(dlist_head *head, dlist_node *node)
Definition ilist.h:429
#define dlist_head_element(type, membername, lhead)
Definition ilist.h:603
static void dlist_delete(dlist_node *node)
Definition ilist.h:405
static uint32 dclist_count(const dclist_head *head)
Definition ilist.h:932
static void dlist_push_head(dlist_head *head, dlist_node *node)
Definition ilist.h:347
#define dlist_foreach_modify(iter, lhead)
Definition ilist.h:640
static bool dlist_is_empty(const dlist_head *head)
Definition ilist.h:336
static void dclist_delete_from(dclist_head *head, dlist_node *node)
Definition ilist.h:763
static dlist_node * dclist_pop_head_node(dclist_head *head)
Definition ilist.h:789
static void dclist_push_head(dclist_head *head, dlist_node *node)
Definition ilist.h:693
static void dclist_init(dclist_head *head)
Definition ilist.h:671
#define dclist_foreach_modify(iter, lhead)
Definition ilist.h:973
#define dlist_container(type, membername, ptr)
Definition ilist.h:593
#define dclist_foreach(iter, lhead)
Definition ilist.h:970
int j
Definition isn.c:78
int i
Definition isn.c:77
void MemoryContextCreate(MemoryContext node, NodeTag tag, MemoryContextMethodID method_id, MemoryContext parent, const char *name)
Definition mcxt.c:1149
MemoryContext TopMemoryContext
Definition mcxt.c:166
void MemoryContextStats(MemoryContext context)
Definition mcxt.c:863
void * MemoryContextAllocationFailure(MemoryContext context, Size size, int flags)
Definition mcxt.c:1198
#define VALGRIND_DESTROY_MEMPOOL(context)
Definition memdebug.h:25
#define VALGRIND_MAKE_MEM_DEFINED(addr, size)
Definition memdebug.h:26
#define VALGRIND_CREATE_MEMPOOL(context, redzones, zeroed)
Definition memdebug.h:24
#define VALGRIND_MEMPOOL_ALLOC(context, addr, size)
Definition memdebug.h:29
#define VALGRIND_MEMPOOL_TRIM(context, addr, size)
Definition memdebug.h:32
#define VALGRIND_MEMPOOL_FREE(context, addr)
Definition memdebug.h:30
#define VALGRIND_MAKE_MEM_NOACCESS(addr, size)
Definition memdebug.h:27
#define VALGRIND_MAKE_MEM_UNDEFINED(addr, size)
Definition memdebug.h:28
void(* MemoryStatsPrintFunc)(MemoryContext context, void *passthru, const char *stats_string, bool print_to_stderr)
Definition memnodes.h:54
#define InvalidAllocSize
Definition memutils.h:47
@ MCTX_SLAB_ID
#define MEMORYCHUNK_MAX_BLOCKOFFSET
#define MEMORYCHUNK_MAX_VALUE
#define MemoryChunkGetPointer(c)
static void * MemoryChunkGetBlock(MemoryChunk *chunk)
#define PointerGetMemoryChunk(p)
static void MemoryChunkSetHdrMask(MemoryChunk *chunk, void *block, Size value, MemoryContextMethodID methodid)
static char * errmsg
#define snprintf
Definition port.h:260
static int fb(int x)
void * SlabAlloc(MemoryContext context, Size size, int flags)
Definition slab.c:659
#define Slab_BLOCKHDRSZ
Definition slab.c:77
static pg_noinline void * SlabAllocFromNewBlock(MemoryContext context, Size size, int flags)
Definition slab.c:565
#define SlabIsValid(set)
Definition slab.c:196
void SlabFree(void *pointer)
Definition slab.c:730
void SlabReset(MemoryContext context)
Definition slab.c:436
#define Slab_CHUNKHDRSZ
Definition slab.c:157
static MemoryChunk * SlabGetNextFreeChunk(SlabContext *slab, SlabBlock *block)
Definition slab.c:271
#define SlabChunkGetPointer(chk)
Definition slab.c:158
MemoryContext SlabContextCreate(MemoryContext parent, const char *name, Size blockSize, Size chunkSize)
Definition slab.c:322
static int32 SlabBlocklistIndex(SlabContext *slab, int nfree)
Definition slab.c:211
static void * SlabAllocSetupNewChunk(MemoryContext context, SlabBlock *block, MemoryChunk *chunk, Size size)
Definition slab.c:523
Size SlabGetChunkSpace(void *pointer)
Definition slab.c:927
#define Slab_CONTEXT_HDRSZ(chunksPerBlock)
Definition slab.c:88
bool SlabIsEmpty(MemoryContext context)
Definition slab.c:952
MemoryContext SlabGetChunkContext(void *pointer)
Definition slab.c:903
static int32 SlabFindNextBlockListIndex(SlabContext *slab)
Definition slab.c:251
pg_noreturn static pg_noinline void SlabAllocInvalidSize(MemoryContext context, Size size)
Definition slab.c:635
#define SlabBlockGetChunk(slab, block, n)
Definition slab.c:165
void * SlabRealloc(void *pointer, Size size, int flags)
Definition slab.c:866
void SlabStats(MemoryContext context, MemoryStatsPrintFunc printfunc, void *passthru, MemoryContextCounters *totals, bool print_to_stderr)
Definition slab.c:969
void SlabDelete(MemoryContext context)
Definition slab.c:506
#define SLAB_BLOCKLIST_COUNT
Definition slab.c:95
#define SlabBlockIsValid(block)
Definition slab.c:202
#define SLAB_MAXIMUM_EMPTY_BLOCKS
Definition slab.c:98
#define free(a)
#define malloc(a)
const char * name
Definition memnodes.h:131
int32 nfree
Definition slab.c:149
MemoryChunk * freehead
Definition slab.c:151
MemoryChunk * unused
Definition slab.c:152
SlabContext * slab
Definition slab.c:148
dlist_node node
Definition slab.c:153
int32 nunused
Definition slab.c:150
dlist_head blocklist[SLAB_BLOCKLIST_COUNT]
Definition slab.c:129
int32 chunksPerBlock
Definition slab.c:110
uint32 fullChunkSize
Definition slab.c:108
MemoryContextData header
Definition slab.c:105
uint32 blockSize
Definition slab.c:109
int32 curBlocklistIndex
Definition slab.c:111
int32 blocklist_shift
Definition slab.c:118
uint32 chunkSize
Definition slab.c:107
dclist_head emptyblocks
Definition slab.c:120
dlist_node * cur
Definition ilist.h:179
Definition type.h:96
const char * name