Skip to content

Commit 0ba8b75

Browse files
committed
Enlarge bit-space for MemoryContextMethodID
Reserve 4 bits for MemoryContextMethodID rather than 3. 3 bits did technically allow a maximum of 8 memory context types, however, we've opted to reserve some bit patterns which left us with only 4 slots, all of which were used. Here we add another bit which frees up 8 slots for future memory context types. In passing, adjust the enum names in MemoryContextMethodID to make it more clear which ones can be used and which ones are reserved. Author: Matthias van de Meent, David Rowley Discussion: https://postgr.es/m/CAApHDvqGSpCU95TmM=Bp=6xjL_nLys4zdZOpfNyWBk97Xrdj2w@mail.gmail.com
1 parent c4ab7da commit 0ba8b75

File tree

4 files changed

+72
-42
lines changed

4 files changed

+72
-42
lines changed

src/backend/utils/mmgr/README

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -395,14 +395,14 @@ relevant MemoryContext as a parameter, operations like free and
395395
realloc are trickier. To make those work, we require all memory
396396
context types to produce allocated chunks that are immediately,
397397
without any padding, preceded by a uint64 value of which the least
398-
significant 3 bits are set to the owning context's MemoryContextMethodID.
398+
significant 4 bits are set to the owning context's MemoryContextMethodID.
399399
This allows the code to determine the correct MemoryContextMethods to
400-
use by looking up the mcxt_methods[] array using the 3 bits as an index
400+
use by looking up the mcxt_methods[] array using the 4 bits as an index
401401
into that array.
402402

403403
If a type of allocator needs additional information about its chunks,
404404
like e.g. the size of the allocation, that information can in turn
405-
either be encoded into the remaining 61 bits of the preceding uint64 value
405+
either be encoded into the remaining 60 bits of the preceding uint64 value
406406
or if more space is required, additional values may be stored directly prior
407407
to the uint64 value. It is up to the context implementation to manage this.
408408

@@ -420,13 +420,20 @@ pfree(void *pointer)
420420

421421
All of the current memory contexts make use of the MemoryChunk header type
422422
which is defined in memutils_memorychunk.h. This suits all of the existing
423-
context types well as it makes use of the remaining 61-bits of the uint64
423+
context types well as it makes use of the remaining 60-bits of the uint64
424424
header to efficiently encode the size of the chunk of memory (or freelist
425425
index, in the case of aset.c) and the number of bytes which must be subtracted
426426
from the chunk in order to obtain a reference to the block that the chunk
427-
belongs to. 30 bits are used for each of these. If more than 30 bits are
428-
required then the memory context must manage that itself. This can be done by
429-
calling the MemoryChunkSetHdrMaskExternal() function on the given chunk.
427+
belongs to. 30 bits are used for each of these, but only a total of 59 bits
428+
as the lowest bit for the chunk to block offset is the same bit as the highest
429+
bit of the chunk size. This overlapping is possible as the relative offset
430+
between the block and the chunk is expected to be a MAXALIGNed value which
431+
guarantees the lowest bit is always 0. If more than 30 bits are required for
432+
each of these fields then the memory context must manage that itself. This
433+
can be done by calling the MemoryChunkSetHdrMaskExternal() function on the
434+
given chunk. Whether a chunk is an external chunk can be determined by the 1
435+
remaining bit from the 64-bit MemoryChunk.
436+
430437
Currently, each memory context type stores large allocations on dedicated
431438
blocks (which always contain only a single chunk). For these, finding the
432439
block is simple as we know that the chunk must be the first on the given

src/backend/utils/mmgr/mcxt.c

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ static Size BogusGetChunkSpace(void *pointer);
3737
/*****************************************************************************
3838
* GLOBAL MEMORY *
3939
*****************************************************************************/
40+
#define BOGUS_MCTX(id) \
41+
[id].free_p = BogusFree, \
42+
[id].realloc = BogusRealloc, \
43+
[id].get_chunk_context = BogusGetChunkContext, \
44+
[id].get_chunk_space = BogusGetChunkSpace
4045

4146
static const MemoryContextMethods mcxt_methods[] = {
4247
/* aset.c */
@@ -97,33 +102,27 @@ static const MemoryContextMethods mcxt_methods[] = {
97102

98103

99104
/*
100-
* Unused (as yet) IDs should have dummy entries here. This allows us to
101-
* fail cleanly if a bogus pointer is passed to pfree or the like. It
105+
* Reserved and unused IDs should have dummy entries here. This allows us
106+
* to fail cleanly if a bogus pointer is passed to pfree or the like. It
102107
* seems sufficient to provide routines for the methods that might get
103108
* invoked from inspection of a chunk (see MCXT_METHOD calls below).
104109
*/
105-
106-
[MCTX_UNUSED1_ID].free_p = BogusFree,
107-
[MCTX_UNUSED1_ID].realloc = BogusRealloc,
108-
[MCTX_UNUSED1_ID].get_chunk_context = BogusGetChunkContext,
109-
[MCTX_UNUSED1_ID].get_chunk_space = BogusGetChunkSpace,
110-
111-
[MCTX_UNUSED2_ID].free_p = BogusFree,
112-
[MCTX_UNUSED2_ID].realloc = BogusRealloc,
113-
[MCTX_UNUSED2_ID].get_chunk_context = BogusGetChunkContext,
114-
[MCTX_UNUSED2_ID].get_chunk_space = BogusGetChunkSpace,
115-
116-
[MCTX_UNUSED3_ID].free_p = BogusFree,
117-
[MCTX_UNUSED3_ID].realloc = BogusRealloc,
118-
[MCTX_UNUSED3_ID].get_chunk_context = BogusGetChunkContext,
119-
[MCTX_UNUSED3_ID].get_chunk_space = BogusGetChunkSpace,
120-
121-
[MCTX_UNUSED4_ID].free_p = BogusFree,
122-
[MCTX_UNUSED4_ID].realloc = BogusRealloc,
123-
[MCTX_UNUSED4_ID].get_chunk_context = BogusGetChunkContext,
124-
[MCTX_UNUSED4_ID].get_chunk_space = BogusGetChunkSpace,
110+
BOGUS_MCTX(MCTX_1_RESERVED_GLIBC_ID),
111+
BOGUS_MCTX(MCTX_2_RESERVED_GLIBC_ID),
112+
BOGUS_MCTX(MCTX_7_UNUSED_ID),
113+
BOGUS_MCTX(MCTX_8_UNUSED_ID),
114+
BOGUS_MCTX(MCTX_9_UNUSED_ID),
115+
BOGUS_MCTX(MCTX_10_UNUSED_ID),
116+
BOGUS_MCTX(MCTX_11_UNUSED_ID),
117+
BOGUS_MCTX(MCTX_12_UNUSED_ID),
118+
BOGUS_MCTX(MCTX_13_UNUSED_ID),
119+
BOGUS_MCTX(MCTX_14_UNUSED_ID),
120+
BOGUS_MCTX(MCTX_0_RESERVED_UNUSEDMEM_ID),
121+
BOGUS_MCTX(MCTX_15_RESERVED_WIPEDMEM_ID)
125122
};
126123

124+
#undef BOGUS_MCTX
125+
127126
/*
128127
* CurrentMemoryContext
129128
* Default memory context for allocations.

src/include/utils/memutils_internal.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,29 @@ extern Size AlignedAllocGetChunkSpace(void *pointer);
104104
*/
105105
typedef enum MemoryContextMethodID
106106
{
107-
MCTX_UNUSED1_ID, /* 000 occurs in never-used memory */
108-
MCTX_UNUSED2_ID, /* glibc malloc'd chunks usually match 001 */
109-
MCTX_UNUSED3_ID, /* glibc malloc'd chunks > 128kB match 010 */
107+
MCTX_0_RESERVED_UNUSEDMEM_ID, /* 0000 occurs in never-used memory */
108+
MCTX_1_RESERVED_GLIBC_ID, /* glibc malloc'd chunks usually match 0001 */
109+
MCTX_2_RESERVED_GLIBC_ID, /* glibc malloc'd chunks > 128kB match 0010 */
110110
MCTX_ASET_ID,
111111
MCTX_GENERATION_ID,
112112
MCTX_SLAB_ID,
113113
MCTX_ALIGNED_REDIRECT_ID,
114-
MCTX_UNUSED4_ID, /* 111 occurs in wipe_mem'd memory */
114+
MCTX_7_UNUSED_ID,
115+
MCTX_8_UNUSED_ID,
116+
MCTX_9_UNUSED_ID,
117+
MCTX_10_UNUSED_ID,
118+
MCTX_11_UNUSED_ID,
119+
MCTX_12_UNUSED_ID,
120+
MCTX_13_UNUSED_ID,
121+
MCTX_14_UNUSED_ID,
122+
MCTX_15_RESERVED_WIPEDMEM_ID /* 1111 occurs in wipe_mem'd memory */
115123
} MemoryContextMethodID;
116124

117125
/*
118126
* The number of bits that 8-byte memory chunk headers can use to encode the
119127
* MemoryContextMethodID.
120128
*/
121-
#define MEMORY_CONTEXT_METHODID_BITS 3
129+
#define MEMORY_CONTEXT_METHODID_BITS 4
122130
#define MEMORY_CONTEXT_METHODID_MASK \
123131
((((uint64) 1) << MEMORY_CONTEXT_METHODID_BITS) - 1)
124132

src/include/utils/memutils_memorychunk.h

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* Although MemoryChunks are used by each of our MemoryContexts, future
1313
* implementations may choose to implement their own method for storing chunk
1414
* headers. The only requirement is that the header ends with an 8-byte value
15-
* which the least significant 3-bits of are set to the MemoryContextMethodID
15+
* which the least significant 4-bits of are set to the MemoryContextMethodID
1616
* of the given context.
1717
*
1818
* By default, a MemoryChunk is 8 bytes in size, however, when
@@ -25,15 +25,23 @@
2525
* used to encode 4 separate pieces of information. Starting with the least
2626
* significant bits of 'hdrmask', the bit space is reserved as follows:
2727
*
28-
* 1. 3-bits to indicate the MemoryContextMethodID as defined by
28+
* 1. 4-bits to indicate the MemoryContextMethodID as defined by
2929
* MEMORY_CONTEXT_METHODID_MASK
3030
* 2. 1-bit to denote an "external" chunk (see below)
3131
* 3. 30-bits reserved for the MemoryContext to use for anything it
32-
* requires. Most MemoryContext likely want to store the size of the
32+
* requires. Most MemoryContexts likely want to store the size of the
3333
* chunk here.
3434
* 4. 30-bits for the number of bytes that must be subtracted from the chunk
3535
* to obtain the address of the block that the chunk is stored on.
3636
*
37+
* If you're paying close attention, you'll notice this adds up to 65 bits
38+
* rather than 64 bits. This is because the highest-order bit of #3 is the
39+
* same bit as the lowest-order bit of #4. We can do this as we insist that
40+
* the chunk and block pointers are both MAXALIGNed, therefore the relative
41+
* offset between those will always be a MAXALIGNed value which means the
42+
* lowest order bit is always 0. When fetching the chunk to block offset we
43+
* mask out the lowest-order bit to ensure it's still zero.
44+
*
3745
* In some cases, for example when memory allocations become large, it's
3846
* possible fields 3 and 4 above are not large enough to store the values
3947
* required for the chunk. In this case, the MemoryContext can choose to mark
@@ -93,10 +101,16 @@
93101
*/
94102
#define MEMORYCHUNK_MAX_BLOCKOFFSET UINT64CONST(0x3FFFFFFF)
95103

104+
/*
105+
* As above, but mask out the lowest-order (always zero) bit as this is shared
106+
* with the MemoryChunkGetValue field.
107+
*/
108+
#define MEMORYCHUNK_BLOCKOFFSET_MASK UINT64CONST(0x3FFFFFFE)
109+
96110
/* define the least significant base-0 bit of each portion of the hdrmask */
97111
#define MEMORYCHUNK_EXTERNAL_BASEBIT MEMORY_CONTEXT_METHODID_BITS
98112
#define MEMORYCHUNK_VALUE_BASEBIT (MEMORYCHUNK_EXTERNAL_BASEBIT + 1)
99-
#define MEMORYCHUNK_BLOCKOFFSET_BASEBIT (MEMORYCHUNK_VALUE_BASEBIT + 30)
113+
#define MEMORYCHUNK_BLOCKOFFSET_BASEBIT (MEMORYCHUNK_VALUE_BASEBIT + 29)
100114

101115
/*
102116
* A magic number for storing in the free bits of an external chunk. This
@@ -131,11 +145,11 @@ typedef struct MemoryChunk
131145
(((hdrmask) >> MEMORYCHUNK_VALUE_BASEBIT) & MEMORYCHUNK_MAX_VALUE)
132146

133147
/*
134-
* We should have used up all the bits here, so the compiler is likely to
135-
* optimize out the & MEMORYCHUNK_MAX_BLOCKOFFSET.
148+
* Shift the block offset down to the 0th bit position and mask off the single
149+
* bit that's shared with the MemoryChunkGetValue field.
136150
*/
137151
#define HdrMaskBlockOffset(hdrmask) \
138-
(((hdrmask) >> MEMORYCHUNK_BLOCKOFFSET_BASEBIT) & MEMORYCHUNK_MAX_BLOCKOFFSET)
152+
(((hdrmask) >> MEMORYCHUNK_BLOCKOFFSET_BASEBIT) & MEMORYCHUNK_BLOCKOFFSET_MASK)
139153

140154
/* For external chunks only, check the magic number matches */
141155
#define HdrMaskCheckMagic(hdrmask) \
@@ -149,6 +163,7 @@ typedef struct MemoryChunk
149163
* The number of bytes between 'block' and 'chunk' must be <=
150164
* MEMORYCHUNK_MAX_BLOCKOFFSET.
151165
* 'value' must be <= MEMORYCHUNK_MAX_VALUE.
166+
* Both 'chunk' and 'block' must be MAXALIGNed pointers.
152167
*/
153168
static inline void
154169
MemoryChunkSetHdrMask(MemoryChunk *chunk, void *block,
@@ -157,7 +172,7 @@ MemoryChunkSetHdrMask(MemoryChunk *chunk, void *block,
157172
Size blockoffset = (char *) chunk - (char *) block;
158173

159174
Assert((char *) chunk >= (char *) block);
160-
Assert(blockoffset <= MEMORYCHUNK_MAX_BLOCKOFFSET);
175+
Assert((blockoffset & MEMORYCHUNK_BLOCKOFFSET_MASK) == blockoffset);
161176
Assert(value <= MEMORYCHUNK_MAX_VALUE);
162177
Assert((int) methodid <= MEMORY_CONTEXT_METHODID_MASK);
163178

@@ -225,6 +240,7 @@ MemoryChunkGetBlock(MemoryChunk *chunk)
225240
}
226241

227242
/* cleanup all internal definitions */
243+
#undef MEMORYCHUNK_BLOCKOFFSET_MASK
228244
#undef MEMORYCHUNK_EXTERNAL_BASEBIT
229245
#undef MEMORYCHUNK_VALUE_BASEBIT
230246
#undef MEMORYCHUNK_BLOCKOFFSET_BASEBIT

0 commit comments

Comments
 (0)