Skip to content

Commit 50a9d71

Browse files
committed
Use doubly-linked block lists in aset.c to reduce large-chunk overhead.
Large chunks (those too large for any palloc freelist) are managed as separate blocks. Formerly, realloc'ing or pfree'ing such a chunk required O(N) time in a context with N blocks, since we had to traipse down the singly-linked block list to locate the block's predecessor before we could fix the list links. This can result in O(N^2) runtime in situations where large numbers of such chunks are manipulated within one context. Cases like that were not foreseen in the original design of aset.c, and indeed didn't arise until fairly recently. But such problems can now occur in reorderbuffer.c and in hash joining, both of which make repeated large requests without scaling up their request size as they do so, and which will free their requests in not-necessarily-LIFO order. To fix, change the block list from singly-linked to doubly-linked. This adds another 4 or 8 bytes to ALLOC_BLOCKHDRSZ, but that doesn't seem like unacceptable overhead, since aset.c's blocks are normally 8K or more, and never less than 1K in current practice. In passing, get rid of some redundant AllocChunkGetPointer() calls in AllocSetRealloc (the compiler might be smart enough to optimize these away anyway, but no need to assume that) and improve AllocSetCheck's checking of block header fields. Back-patch to 9.4 where reorderbuffer.c appeared. We could take this further back, but currently there's no evidence that it would be useful. Discussion: https://postgr.es/m/CAMkU=1x1hvue1XYrZoWk_omG0Ja5nBvTdvgrOeVkkeqs71CV8g@mail.gmail.com
1 parent 197a4c4 commit 50a9d71

File tree

1 file changed

+66
-42
lines changed

1 file changed

+66
-42
lines changed

src/backend/utils/mmgr/aset.c

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ typedef AllocSetContext *AllocSet;
201201
typedef struct AllocBlockData
202202
{
203203
AllocSet aset; /* aset that owns this block */
204-
AllocBlock next; /* next block in aset's blocks list */
204+
AllocBlock prev; /* prev block in aset's blocks list, if any */
205+
AllocBlock next; /* next block in aset's blocks list, if any */
205206
char *freeptr; /* start of free space in this block */
206207
char *endptr; /* end of space in this block */
207208
} AllocBlockData;
@@ -508,7 +509,10 @@ AllocSetContextCreate(MemoryContext parent,
508509
block->aset = set;
509510
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
510511
block->endptr = ((char *) block) + blksize;
512+
block->prev = NULL;
511513
block->next = set->blocks;
514+
if (block->next)
515+
block->next->prev = block;
512516
set->blocks = block;
513517
/* Mark block as not to be released at reset time */
514518
set->keeper = block;
@@ -590,6 +594,7 @@ AllocSetReset(MemoryContext context)
590594
VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
591595
#endif
592596
block->freeptr = datastart;
597+
block->prev = NULL;
593598
block->next = NULL;
594599
}
595600
else
@@ -696,16 +701,20 @@ AllocSetAlloc(MemoryContext context, Size size)
696701
#endif
697702

698703
/*
699-
* Stick the new block underneath the active allocation block, so that
700-
* we don't lose the use of the space remaining therein.
704+
* Stick the new block underneath the active allocation block, if any,
705+
* so that we don't lose the use of the space remaining therein.
701706
*/
702707
if (set->blocks != NULL)
703708
{
709+
block->prev = set->blocks;
704710
block->next = set->blocks->next;
711+
if (block->next)
712+
block->next->prev = block;
705713
set->blocks->next = block;
706714
}
707715
else
708716
{
717+
block->prev = NULL;
709718
block->next = NULL;
710719
set->blocks = block;
711720
}
@@ -886,7 +895,10 @@ AllocSetAlloc(MemoryContext context, Size size)
886895
VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
887896
blksize - ALLOC_BLOCKHDRSZ);
888897

898+
block->prev = NULL;
889899
block->next = set->blocks;
900+
if (block->next)
901+
block->next->prev = block;
890902
set->blocks = block;
891903
}
892904

@@ -946,29 +958,28 @@ AllocSetFree(MemoryContext context, void *pointer)
946958
{
947959
/*
948960
* Big chunks are certain to have been allocated as single-chunk
949-
* blocks. Find the containing block and return it to malloc().
961+
* blocks. Just unlink that block and return it to malloc().
950962
*/
951-
AllocBlock block = set->blocks;
952-
AllocBlock prevblock = NULL;
963+
AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ);
953964

954-
while (block != NULL)
955-
{
956-
if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ))
957-
break;
958-
prevblock = block;
959-
block = block->next;
960-
}
961-
if (block == NULL)
965+
/*
966+
* Try to verify that we have a sane block pointer: it should
967+
* reference the correct aset, and freeptr and endptr should point
968+
* just past the chunk.
969+
*/
970+
if (block->aset != set ||
971+
block->freeptr != block->endptr ||
972+
block->freeptr != ((char *) block) +
973+
(chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ))
962974
elog(ERROR, "could not find block containing chunk %p", chunk);
963-
/* let's just make sure chunk is the only one in the block */
964-
Assert(block->freeptr == ((char *) block) +
965-
(chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ));
966975

967976
/* OK, remove block from aset's list and free it */
968-
if (prevblock == NULL)
969-
set->blocks = block->next;
977+
if (block->prev)
978+
block->prev->next = block->next;
970979
else
971-
prevblock->next = block->next;
980+
set->blocks = block->next;
981+
if (block->next)
982+
block->next->prev = block->prev;
972983
#ifdef CLOBBER_FREED_MEMORY
973984
wipe_mem(block, block->freeptr - ((char *) block));
974985
#endif
@@ -1074,27 +1085,24 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
10741085
if (oldsize > set->allocChunkLimit)
10751086
{
10761087
/*
1077-
* The chunk must have been allocated as a single-chunk block. Find
1078-
* the containing block and use realloc() to make it bigger with
1079-
* minimum space wastage.
1088+
* The chunk must have been allocated as a single-chunk block. Use
1089+
* realloc() to make the containing block bigger with minimum space
1090+
* wastage.
10801091
*/
1081-
AllocBlock block = set->blocks;
1082-
AllocBlock prevblock = NULL;
1092+
AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ);
10831093
Size chksize;
10841094
Size blksize;
10851095

1086-
while (block != NULL)
1087-
{
1088-
if (chunk == (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ))
1089-
break;
1090-
prevblock = block;
1091-
block = block->next;
1092-
}
1093-
if (block == NULL)
1096+
/*
1097+
* Try to verify that we have a sane block pointer: it should
1098+
* reference the correct aset, and freeptr and endptr should point
1099+
* just past the chunk.
1100+
*/
1101+
if (block->aset != set ||
1102+
block->freeptr != block->endptr ||
1103+
block->freeptr != ((char *) block) +
1104+
(chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ))
10941105
elog(ERROR, "could not find block containing chunk %p", chunk);
1095-
/* let's just make sure chunk is the only one in the block */
1096-
Assert(block->freeptr == ((char *) block) +
1097-
(chunk->size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ));
10981106

10991107
/* Do the realloc */
11001108
chksize = MAXALIGN(size);
@@ -1107,10 +1115,12 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
11071115
/* Update pointers since block has likely been moved */
11081116
chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
11091117
pointer = AllocChunkGetPointer(chunk);
1110-
if (prevblock == NULL)
1111-
set->blocks = block;
1118+
if (block->prev)
1119+
block->prev->next = block;
11121120
else
1113-
prevblock->next = block;
1121+
set->blocks = block;
1122+
if (block->next)
1123+
block->next->prev = block;
11141124
chunk->size = chksize;
11151125

11161126
#ifdef MEMORY_CONTEXT_CHECKING
@@ -1134,7 +1144,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
11341144

11351145
/* set mark to catch clobber of "unused" space */
11361146
if (size < chunk->size)
1137-
set_sentinel(AllocChunkGetPointer(chunk), size);
1147+
set_sentinel(pointer, size);
11381148
#else /* !MEMORY_CONTEXT_CHECKING */
11391149

11401150
/*
@@ -1147,7 +1157,8 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size)
11471157

11481158
/* Make any trailing alignment padding NOACCESS. */
11491159
VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, chksize - size);
1150-
return AllocChunkGetPointer(chunk);
1160+
1161+
return pointer;
11511162
}
11521163
else
11531164
{
@@ -1284,9 +1295,12 @@ AllocSetCheck(MemoryContext context)
12841295
{
12851296
AllocSet set = (AllocSet) context;
12861297
char *name = set->header.name;
1298+
AllocBlock prevblock;
12871299
AllocBlock block;
12881300

1289-
for (block = set->blocks; block != NULL; block = block->next)
1301+
for (prevblock = NULL, block = set->blocks;
1302+
block != NULL;
1303+
prevblock = block, block = block->next)
12901304
{
12911305
char *bpoz = ((char *) block) + ALLOC_BLOCKHDRSZ;
12921306
long blk_used = block->freeptr - bpoz;
@@ -1303,6 +1317,16 @@ AllocSetCheck(MemoryContext context)
13031317
name, block);
13041318
}
13051319

1320+
/*
1321+
* Check block header fields
1322+
*/
1323+
if (block->aset != set ||
1324+
block->prev != prevblock ||
1325+
block->freeptr < bpoz ||
1326+
block->freeptr > block->endptr)
1327+
elog(WARNING, "problem in alloc set %s: corrupt header in block %p",
1328+
name, block);
1329+
13061330
/*
13071331
* Chunk walker
13081332
*/

0 commit comments

Comments
 (0)