Shrink memory contexts struct sizes
authorDavid Rowley <drowley@postgresql.org>
Sun, 16 Jul 2023 23:16:56 +0000 (11:16 +1200)
committerDavid Rowley <drowley@postgresql.org>
Sun, 16 Jul 2023 23:16:56 +0000 (11:16 +1200)
Here we reduce the block size fields in AllocSetContext, GenerationContext
and SlabContext from Size down to uint32.  Ever since c6e0fe1f2, blocks
for non-dedicated palloc chunks can no longer be larger than 1GB, so
there's no need to store the various block size fields as 64-bit values.
32 bits are enough to store 2^30.

Here we also further reduce the memory context struct sizes by getting rid
of the 'keeper' field which stores a pointer to the context's keeper
block.  All the context types which have this field always allocate the
keeper block in the same allocation as the memory context itself, so the
keeper block always comes right at the end of the context struct.  Add
some macros to calculate that address rather than storing it in the
context.

Overall, in AllocSetContext and GenerationContext, this saves 20 bytes on
64-bit builds which for ALLOCSET_SMALL_SIZES can sometimes mean the
difference between having to allocate a 2nd block and storing all the
required allocations on the keeper block alone.  Such contexts are used
in relcache to store cache entries for indexes, of which there can be
a large number in a single backend.

Author: Melih Mutlu
Reviewed-by: David Rowley
Discussion: https://postgr.es/m/CAGPVpCSOW3uJ1QJmsMR9_oE3X7fG_z4q0AoU4R_w+2RzvroPFg@mail.gmail.com

src/backend/utils/mmgr/aset.c
src/backend/utils/mmgr/generation.c
src/backend/utils/mmgr/slab.c

index 0bbbf93672eb2e59b417a1e79814f845066526a0..c3affaf5a8a5a43c45a1da6645c5d38fdfe9d956 100644 (file)
@@ -156,11 +156,10 @@ typedef struct AllocSetContext
        AllocBlock      blocks;                 /* head of list of blocks in this set */
        MemoryChunk *freelist[ALLOCSET_NUM_FREELISTS];  /* free chunk lists */
        /* Allocation parameters for this context: */
-       Size            initBlockSize;  /* initial block size */
-       Size            maxBlockSize;   /* maximum block size */
-       Size            nextBlockSize;  /* next block size to allocate */
-       Size            allocChunkLimit;        /* effective chunk size limit */
-       AllocBlock      keeper;                 /* keep this block over resets */
+       uint32          initBlockSize;  /* initial block size */
+       uint32          maxBlockSize;   /* maximum block size */
+       uint32          nextBlockSize;  /* next block size to allocate */
+       uint32          allocChunkLimit;        /* effective chunk size limit */
        /* freelist this context could be put in, or -1 if not a candidate: */
        int                     freeListIndex;  /* index in context_freelists[], or -1 */
 } AllocSetContext;
@@ -241,6 +240,13 @@ typedef struct AllocBlockData
  */
 #define MAX_FREE_CONTEXTS 100  /* arbitrary limit on freelist length */
 
+/* Obtain the keeper block for an allocation set */
+#define KeeperBlock(set) \
+       ((AllocBlock) (((char *) set) + MAXALIGN(sizeof(AllocSetContext))))
+
+/* Check if the block is the keeper block of the given allocation set */
+#define IsKeeperBlock(set, block) ((block) == (KeeperBlock(set)))
+
 typedef struct AllocSetFreeList
 {
        int                     num_free;               /* current list length */
@@ -417,7 +423,7 @@ AllocSetContextCreateInternal(MemoryContext parent,
                                                                name);
 
                        ((MemoryContext) set)->mem_allocated =
-                               set->keeper->endptr - ((char *) set);
+                               KeeperBlock(set)->endptr - ((char *) set);
 
                        return (MemoryContext) set;
                }
@@ -453,7 +459,7 @@ AllocSetContextCreateInternal(MemoryContext parent,
         */
 
        /* Fill in the initial block's block header */
-       block = (AllocBlock) (((char *) set) + MAXALIGN(sizeof(AllocSetContext)));
+       block = KeeperBlock(set);
        block->aset = set;
        block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
        block->endptr = ((char *) set) + firstBlockSize;
@@ -465,15 +471,13 @@ AllocSetContextCreateInternal(MemoryContext parent,
 
        /* Remember block as part of block list */
        set->blocks = block;
-       /* Mark block as not to be released at reset time */
-       set->keeper = block;
 
        /* Finish filling in aset-specific parts of the context header */
        MemSetAligned(set->freelist, 0, sizeof(set->freelist));
 
-       set->initBlockSize = initBlockSize;
-       set->maxBlockSize = maxBlockSize;
-       set->nextBlockSize = initBlockSize;
+       set->initBlockSize = (uint32) initBlockSize;
+       set->maxBlockSize = (uint32) maxBlockSize;
+       set->nextBlockSize = (uint32) initBlockSize;
        set->freeListIndex = freeListIndex;
 
        /*
@@ -544,7 +548,7 @@ AllocSetReset(MemoryContext context)
 #endif
 
        /* Remember keeper block size for Assert below */
-       keepersize = set->keeper->endptr - ((char *) set);
+       keepersize = KeeperBlock(set)->endptr - ((char *) set);
 
        /* Clear chunk freelists */
        MemSetAligned(set->freelist, 0, sizeof(set->freelist));
@@ -552,13 +556,13 @@ AllocSetReset(MemoryContext context)
        block = set->blocks;
 
        /* New blocks list will be just the keeper block */
-       set->blocks = set->keeper;
+       set->blocks = KeeperBlock(set);
 
        while (block != NULL)
        {
                AllocBlock      next = block->next;
 
-               if (block == set->keeper)
+               if (IsKeeperBlock(set, block))
                {
                        /* Reset the block, but don't return it to malloc */
                        char       *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ;
@@ -614,7 +618,7 @@ AllocSetDelete(MemoryContext context)
 #endif
 
        /* Remember keeper block size for Assert below */
-       keepersize = set->keeper->endptr - ((char *) set);
+       keepersize = KeeperBlock(set)->endptr - ((char *) set);
 
        /*
         * If the context is a candidate for a freelist, put it into that freelist
@@ -663,14 +667,14 @@ AllocSetDelete(MemoryContext context)
        {
                AllocBlock      next = block->next;
 
-               if (block != set->keeper)
+               if (!IsKeeperBlock(set, block))
                        context->mem_allocated -= block->endptr - ((char *) block);
 
 #ifdef CLOBBER_FREED_MEMORY
                wipe_mem(block, block->freeptr - ((char *) block));
 #endif
 
-               if (block != set->keeper)
+               if (!IsKeeperBlock(set, block))
                        free(block);
 
                block = next;
@@ -1547,7 +1551,7 @@ AllocSetCheck(MemoryContext context)
                long            nchunks = 0;
                bool            has_external_chunk = false;
 
-               if (set->keeper == block)
+               if (IsKeeperBlock(set, block))
                        total_allocated += block->endptr - ((char *) set);
                else
                        total_allocated += block->endptr - ((char *) block);
@@ -1557,7 +1561,7 @@ AllocSetCheck(MemoryContext context)
                 */
                if (!blk_used)
                {
-                       if (set->keeper != block)
+                       if (!IsKeeperBlock(set, block))
                                elog(WARNING, "problem in alloc set %s: empty block %p",
                                         name, block);
                }
index 4fb8663cd6b1d9f4d7795e04cd96a543158c75f9..92401ccf738e8bb275bc9629f995b35e8ffd59c8 100644 (file)
@@ -61,17 +61,16 @@ typedef struct GenerationContext
        MemoryContextData header;       /* Standard memory-context fields */
 
        /* Generational context parameters */
-       Size            initBlockSize;  /* initial block size */
-       Size            maxBlockSize;   /* maximum block size */
-       Size            nextBlockSize;  /* next block size to allocate */
-       Size            allocChunkLimit;        /* effective chunk size limit */
+       uint32          initBlockSize;  /* initial block size */
+       uint32          maxBlockSize;   /* maximum block size */
+       uint32          nextBlockSize;  /* next block size to allocate */
+       uint32          allocChunkLimit;        /* effective chunk size limit */
 
        GenerationBlock *block;         /* current (most recently allocated) block, or
                                                                 * NULL if we've just freed the most recent
                                                                 * block */
        GenerationBlock *freeblock; /* pointer to a block that's being recycled,
                                                                 * or NULL if there's no such block. */
-       GenerationBlock *keeper;        /* keep this block over resets */
        dlist_head      blocks;                 /* list of blocks */
 } GenerationContext;
 
@@ -120,6 +119,14 @@ struct GenerationBlock
 #define ExternalChunkGetBlock(chunk) \
        (GenerationBlock *) ((char *) chunk - Generation_BLOCKHDRSZ)
 
+/* Obtain the keeper block for a generation context */
+#define KeeperBlock(set) \
+       ((GenerationBlock *) (((char *) set) + \
+       MAXALIGN(sizeof(GenerationContext))))
+
+/* Check if the block is the keeper block of the given generation context */
+#define IsKeeperBlock(set, block) ((block) == (KeeperBlock(set)))
+
 /* Inlined helper functions */
 static inline void GenerationBlockInit(GenerationContext *context,
                                                                           GenerationBlock *block,
@@ -214,7 +221,7 @@ GenerationContextCreate(MemoryContext parent,
        dlist_init(&set->blocks);
 
        /* Fill in the initial block's block header */
-       block = (GenerationBlock *) (((char *) set) + MAXALIGN(sizeof(GenerationContext)));
+       block = KeeperBlock(set);
        /* determine the block size and initialize it */
        firstBlockSize = allocSize - MAXALIGN(sizeof(GenerationContext));
        GenerationBlockInit(set, block, firstBlockSize);
@@ -228,13 +235,10 @@ GenerationContextCreate(MemoryContext parent,
        /* No free block, yet */
        set->freeblock = NULL;
 
-       /* Mark block as not to be released at reset time */
-       set->keeper = block;
-
        /* Fill in GenerationContext-specific header fields */
-       set->initBlockSize = initBlockSize;
-       set->maxBlockSize = maxBlockSize;
-       set->nextBlockSize = initBlockSize;
+       set->initBlockSize = (uint32) initBlockSize;
+       set->maxBlockSize = (uint32) maxBlockSize;
+       set->nextBlockSize = (uint32) initBlockSize;
 
        /*
         * Compute the allocation chunk size limit for this context.
@@ -294,14 +298,14 @@ GenerationReset(MemoryContext context)
        {
                GenerationBlock *block = dlist_container(GenerationBlock, node, miter.cur);
 
-               if (block == set->keeper)
+               if (IsKeeperBlock(set, block))
                        GenerationBlockMarkEmpty(block);
                else
                        GenerationBlockFree(set, block);
        }
 
        /* set it so new allocations to make use of the keeper block */
-       set->block = set->keeper;
+       set->block = KeeperBlock(set);
 
        /* Reset block size allocation sequence, too */
        set->nextBlockSize = set->initBlockSize;
@@ -440,10 +444,10 @@ GenerationAlloc(MemoryContext context, Size size)
                         */
                        set->freeblock = NULL;
                }
-               else if (GenerationBlockIsEmpty(set->keeper) &&
-                                GenerationBlockFreeBytes(set->keeper) >= required_size)
+               else if (GenerationBlockIsEmpty(KeeperBlock(set)) &&
+                                GenerationBlockFreeBytes(KeeperBlock(set)) >= required_size)
                {
-                       block = set->keeper;
+                       block = KeeperBlock(set);
                }
                else
                {
@@ -594,7 +598,7 @@ static inline void
 GenerationBlockFree(GenerationContext *set, GenerationBlock *block)
 {
        /* Make sure nobody tries to free the keeper block */
-       Assert(block != set->keeper);
+       Assert(!IsKeeperBlock(set, block));
        /* We shouldn't be freeing the freeblock either */
        Assert(block != set->freeblock);
 
@@ -691,7 +695,7 @@ GenerationFree(void *pointer)
        set = block->context;
 
        /* Don't try to free the keeper block, just mark it empty */
-       if (block == set->keeper)
+       if (IsKeeperBlock(set, block))
        {
                GenerationBlockMarkEmpty(block);
                return;
index 718dd2ba03c529e6b159cbda49693d1537384863..40c1d401c4cf7fc24a1be49fee25d05b935121a2 100644 (file)
@@ -104,9 +104,9 @@ typedef struct SlabContext
 {
        MemoryContextData header;       /* Standard memory-context fields */
        /* Allocation parameters for this context: */
-       Size            chunkSize;              /* the requested (non-aligned) chunk size */
-       Size            fullChunkSize;  /* chunk size with chunk header and alignment */
-       Size            blockSize;              /* the size to make each block of chunks */
+       uint32          chunkSize;              /* the requested (non-aligned) chunk size */
+       uint32          fullChunkSize;  /* chunk size with chunk header and alignment */
+       uint32          blockSize;              /* the size to make each block of chunks */
        int32           chunksPerBlock; /* number of chunks that fit in 1 block */
        int32           curBlocklistIndex;      /* index into the blocklist[] element
                                                                         * containing the fullest, blocks */
@@ -314,7 +314,9 @@ SlabGetNextFreeChunk(SlabContext *slab, SlabBlock *block)
  * blockSize: allocation block size
  * chunkSize: allocation chunk size
  *
- * The MAXALIGN(chunkSize) may not exceed MEMORYCHUNK_MAX_VALUE
+ * The Slab_CHUNKHDRSZ + MAXALIGN(chunkSize + 1) may not exceed
+ * MEMORYCHUNK_MAX_VALUE.
+ * 'blockSize' may not exceed MEMORYCHUNK_MAX_BLOCKOFFSET.
  */
 MemoryContext
 SlabContextCreate(MemoryContext parent,
@@ -330,7 +332,7 @@ SlabContextCreate(MemoryContext parent,
        /* ensure MemoryChunk's size is properly maxaligned */
        StaticAssertDecl(Slab_CHUNKHDRSZ == MAXALIGN(Slab_CHUNKHDRSZ),
                                         "sizeof(MemoryChunk) is not maxaligned");
-       Assert(MAXALIGN(chunkSize) <= MEMORYCHUNK_MAX_VALUE);
+       Assert(blockSize <= MEMORYCHUNK_MAX_BLOCKOFFSET);
 
        /*
         * Ensure there's enough space to store the pointer to the next free chunk
@@ -347,6 +349,8 @@ SlabContextCreate(MemoryContext parent,
        fullChunkSize = Slab_CHUNKHDRSZ + MAXALIGN(chunkSize);
 #endif
 
+       Assert(fullChunkSize <= MEMORYCHUNK_MAX_VALUE);
+
        /* compute the number of chunks that will fit on each block */
        chunksPerBlock = (blockSize - Slab_BLOCKHDRSZ) / fullChunkSize;
 
@@ -374,9 +378,9 @@ SlabContextCreate(MemoryContext parent,
         */
 
        /* Fill in SlabContext-specific header fields */
-       slab->chunkSize = chunkSize;
-       slab->fullChunkSize = fullChunkSize;
-       slab->blockSize = blockSize;
+       slab->chunkSize = (uint32) chunkSize;
+       slab->fullChunkSize = (uint32) fullChunkSize;
+       slab->blockSize = (uint32) blockSize;
        slab->chunksPerBlock = chunksPerBlock;
        slab->curBlocklistIndex = 0;
 
@@ -506,7 +510,7 @@ SlabAlloc(MemoryContext context, Size size)
 
        /* make sure we only allow correct request size */
        if (unlikely(size != slab->chunkSize))
-               elog(ERROR, "unexpected alloc chunk size %zu (expected %zu)",
+               elog(ERROR, "unexpected alloc chunk size %zu (expected %u)",
                         size, slab->chunkSize);
 
        /*