Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 263865a

Browse files
committed
Permit super-MaxAllocSize allocations with MemoryContextAllocHuge().
The MaxAllocSize guard is convenient for most callers, because it reduces the need for careful attention to overflow, data type selection, and the SET_VARSIZE() limit. A handful of callers are happy to navigate those hazards in exchange for the ability to allocate a larger chunk. Introduce MemoryContextAllocHuge() and repalloc_huge(). Use this in tuplesort.c and tuplestore.c, enabling internal sorts of up to INT_MAX tuples, a factor-of-48 increase. In particular, B-tree index builds can now benefit from much-larger maintenance_work_mem settings. Reviewed by Stephen Frost, Simon Riggs and Jeff Janes.
1 parent 9ef86cd commit 263865a

File tree

7 files changed

+183
-98
lines changed

7 files changed

+183
-98
lines changed

src/backend/utils/mmgr/aset.c

+5
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ AllocSetContextCreate(MemoryContext parent,
458458
maxBlockSize = MAXALIGN(maxBlockSize);
459459
if (maxBlockSize < initBlockSize)
460460
maxBlockSize = initBlockSize;
461+
Assert(AllocHugeSizeIsValid(maxBlockSize)); /* must be safe to double */
461462
context->initBlockSize = initBlockSize;
462463
context->maxBlockSize = maxBlockSize;
463464
context->nextBlockSize = initBlockSize;
@@ -643,6 +644,10 @@ AllocSetDelete(MemoryContext context)
643644
* AllocSetAlloc
644645
* Returns pointer to allocated memory of given size; memory is added
645646
* to the set.
647+
*
648+
* No request may exceed:
649+
* MAXALIGN_DOWN(SIZE_MAX) - ALLOC_BLOCKHDRSZ - ALLOC_CHUNKHDRSZ
650+
* All callers use a much-lower limit.
646651
*/
647652
static void *
648653
AllocSetAlloc(MemoryContext context, Size size)

src/backend/utils/mmgr/mcxt.c

+66-8
Original file line numberDiff line numberDiff line change
@@ -455,14 +455,7 @@ MemoryContextContains(MemoryContext context, void *pointer)
455455
header = (StandardChunkHeader *)
456456
((char *) pointer - STANDARDCHUNKHEADERSIZE);
457457

458-
/*
459-
* If the context link doesn't match then we certainly have a non-member
460-
* chunk. Also check for a reasonable-looking size as extra guard against
461-
* being fooled by bogus pointers.
462-
*/
463-
if (header->context == context && AllocSizeIsValid(header->size))
464-
return true;
465-
return false;
458+
return header->context == context;
466459
}
467460

468461
/*--------------------
@@ -757,6 +750,71 @@ repalloc(void *pointer, Size size)
757750
return ret;
758751
}
759752

753+
/*
754+
* MemoryContextAllocHuge
755+
* Allocate (possibly-expansive) space within the specified context.
756+
*
757+
* See considerations in comment at MaxAllocHugeSize.
758+
*/
759+
void *
760+
MemoryContextAllocHuge(MemoryContext context, Size size)
761+
{
762+
void *ret;
763+
764+
AssertArg(MemoryContextIsValid(context));
765+
766+
if (!AllocHugeSizeIsValid(size))
767+
elog(ERROR, "invalid memory alloc request size %lu",
768+
(unsigned long) size);
769+
770+
context->isReset = false;
771+
772+
ret = (*context->methods->alloc) (context, size);
773+
VALGRIND_MEMPOOL_ALLOC(context, ret, size);
774+
775+
return ret;
776+
}
777+
778+
/*
779+
* repalloc_huge
780+
* Adjust the size of a previously allocated chunk, permitting a large
781+
* value. The previous allocation need not have been "huge".
782+
*/
783+
void *
784+
repalloc_huge(void *pointer, Size size)
785+
{
786+
MemoryContext context;
787+
void *ret;
788+
789+
if (!AllocHugeSizeIsValid(size))
790+
elog(ERROR, "invalid memory alloc request size %lu",
791+
(unsigned long) size);
792+
793+
/*
794+
* Try to detect bogus pointers handed to us, poorly though we can.
795+
* Presumably, a pointer that isn't MAXALIGNED isn't pointing at an
796+
* allocated chunk.
797+
*/
798+
Assert(pointer != NULL);
799+
Assert(pointer == (void *) MAXALIGN(pointer));
800+
801+
/*
802+
* OK, it's probably safe to look at the chunk header.
803+
*/
804+
context = ((StandardChunkHeader *)
805+
((char *) pointer - STANDARDCHUNKHEADERSIZE))->context;
806+
807+
AssertArg(MemoryContextIsValid(context));
808+
809+
/* isReset must be false already */
810+
Assert(!context->isReset);
811+
812+
ret = (*context->methods->realloc) (context, pointer, size);
813+
VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
814+
815+
return ret;
816+
}
817+
760818
/*
761819
* MemoryContextStrdup
762820
* Like strdup(), but allocate from the specified context

src/backend/utils/sort/tuplesort.c

+49-42
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,8 @@ struct Tuplesortstate
211211
* tuples to return? */
212212
bool boundUsed; /* true if we made use of a bounded heap */
213213
int bound; /* if bounded, the maximum number of tuples */
214-
long availMem; /* remaining memory available, in bytes */
215-
long allowedMem; /* total memory allowed, in bytes */
214+
Size availMem; /* remaining memory available, in bytes */
215+
Size allowedMem; /* total memory allowed, in bytes */
216216
int maxTapes; /* number of tapes (Knuth's T) */
217217
int tapeRange; /* maxTapes-1 (Knuth's P) */
218218
MemoryContext sortcontext; /* memory context holding all sort data */
@@ -308,7 +308,7 @@ struct Tuplesortstate
308308
int *mergenext; /* first preread tuple for each source */
309309
int *mergelast; /* last preread tuple for each source */
310310
int *mergeavailslots; /* slots left for prereading each tape */
311-
long *mergeavailmem; /* availMem for prereading each tape */
311+
Size *mergeavailmem; /* availMem for prereading each tape */
312312
int mergefreelist; /* head of freelist of recycled slots */
313313
int mergefirstfree; /* first slot never used in this merge */
314314

@@ -961,25 +961,26 @@ tuplesort_end(Tuplesortstate *state)
961961
}
962962

963963
/*
964-
* Grow the memtuples[] array, if possible within our memory constraint.
965-
* Return TRUE if we were able to enlarge the array, FALSE if not.
964+
* Grow the memtuples[] array, if possible within our memory constraint. We
965+
* must not exceed INT_MAX tuples in memory or the caller-provided memory
966+
* limit. Return TRUE if we were able to enlarge the array, FALSE if not.
966967
*
967-
* Normally, at each increment we double the size of the array. When we no
968-
* longer have enough memory to do that, we attempt one last, smaller increase
969-
* (and then clear the growmemtuples flag so we don't try any more). That
970-
* allows us to use allowedMem as fully as possible; sticking to the pure
971-
* doubling rule could result in almost half of allowedMem going unused.
972-
* Because availMem moves around with tuple addition/removal, we need some
973-
* rule to prevent making repeated small increases in memtupsize, which would
974-
* just be useless thrashing. The growmemtuples flag accomplishes that and
975-
* also prevents useless recalculations in this function.
968+
* Normally, at each increment we double the size of the array. When doing
969+
* that would exceed a limit, we attempt one last, smaller increase (and then
970+
* clear the growmemtuples flag so we don't try any more). That allows us to
971+
* use memory as fully as permitted; sticking to the pure doubling rule could
972+
* result in almost half going unused. Because availMem moves around with
973+
* tuple addition/removal, we need some rule to prevent making repeated small
974+
* increases in memtupsize, which would just be useless thrashing. The
975+
* growmemtuples flag accomplishes that and also prevents useless
976+
* recalculations in this function.
976977
*/
977978
static bool
978979
grow_memtuples(Tuplesortstate *state)
979980
{
980981
int newmemtupsize;
981982
int memtupsize = state->memtupsize;
982-
long memNowUsed = state->allowedMem - state->availMem;
983+
Size memNowUsed = state->allowedMem - state->availMem;
983984

984985
/* Forget it if we've already maxed out memtuples, per comment above */
985986
if (!state->growmemtuples)
@@ -989,14 +990,16 @@ grow_memtuples(Tuplesortstate *state)
989990
if (memNowUsed <= state->availMem)
990991
{
991992
/*
992-
* It is surely safe to double memtupsize if we've used no more than
993-
* half of allowedMem.
994-
*
995-
* Note: it might seem that we need to worry about memtupsize * 2
996-
* overflowing an int, but the MaxAllocSize clamp applied below
997-
* ensures the existing memtupsize can't be large enough for that.
993+
* We've used no more than half of allowedMem; double our usage,
994+
* clamping at INT_MAX.
998995
*/
999-
newmemtupsize = memtupsize * 2;
996+
if (memtupsize < INT_MAX / 2)
997+
newmemtupsize = memtupsize * 2;
998+
else
999+
{
1000+
newmemtupsize = INT_MAX;
1001+
state->growmemtuples = false;
1002+
}
10001003
}
10011004
else
10021005
{
@@ -1012,24 +1015,27 @@ grow_memtuples(Tuplesortstate *state)
10121015
* we've already seen, and thus we can extrapolate from the space
10131016
* consumption so far to estimate an appropriate new size for the
10141017
* memtuples array. The optimal value might be higher or lower than
1015-
* this estimate, but it's hard to know that in advance.
1018+
* this estimate, but it's hard to know that in advance. We again
1019+
* clamp at INT_MAX tuples.
10161020
*
10171021
* This calculation is safe against enlarging the array so much that
10181022
* LACKMEM becomes true, because the memory currently used includes
10191023
* the present array; thus, there would be enough allowedMem for the
10201024
* new array elements even if no other memory were currently used.
10211025
*
10221026
* We do the arithmetic in float8, because otherwise the product of
1023-
* memtupsize and allowedMem could overflow. (A little algebra shows
1024-
* that grow_ratio must be less than 2 here, so we are not risking
1025-
* integer overflow this way.) Any inaccuracy in the result should be
1026-
* insignificant; but even if we computed a completely insane result,
1027-
* the checks below will prevent anything really bad from happening.
1027+
* memtupsize and allowedMem could overflow. Any inaccuracy in the
1028+
* result should be insignificant; but even if we computed a
1029+
* completely insane result, the checks below will prevent anything
1030+
* really bad from happening.
10281031
*/
10291032
double grow_ratio;
10301033

10311034
grow_ratio = (double) state->allowedMem / (double) memNowUsed;
1032-
newmemtupsize = (int) (memtupsize * grow_ratio);
1035+
if (memtupsize * grow_ratio < INT_MAX)
1036+
newmemtupsize = (int) (memtupsize * grow_ratio);
1037+
else
1038+
newmemtupsize = INT_MAX;
10331039

10341040
/* We won't make any further enlargement attempts */
10351041
state->growmemtuples = false;
@@ -1040,12 +1046,13 @@ grow_memtuples(Tuplesortstate *state)
10401046
goto noalloc;
10411047

10421048
/*
1043-
* On a 64-bit machine, allowedMem could be more than MaxAllocSize. Clamp
1044-
* to ensure our request won't be rejected by palloc.
1049+
* On a 32-bit machine, allowedMem could exceed MaxAllocHugeSize. Clamp
1050+
* to ensure our request won't be rejected. Note that we can easily
1051+
* exhaust address space before facing this outcome.
10451052
*/
1046-
if ((Size) newmemtupsize >= MaxAllocSize / sizeof(SortTuple))
1053+
if ((Size) newmemtupsize >= MaxAllocHugeSize / sizeof(SortTuple))
10471054
{
1048-
newmemtupsize = (int) (MaxAllocSize / sizeof(SortTuple));
1055+
newmemtupsize = (int) (MaxAllocHugeSize / sizeof(SortTuple));
10491056
state->growmemtuples = false; /* can't grow any more */
10501057
}
10511058

@@ -1060,15 +1067,15 @@ grow_memtuples(Tuplesortstate *state)
10601067
* palloc would be treating both old and new arrays as separate chunks.
10611068
* But we'll check LACKMEM explicitly below just in case.)
10621069
*/
1063-
if (state->availMem < (long) ((newmemtupsize - memtupsize) * sizeof(SortTuple)))
1070+
if (state->availMem < (Size) ((newmemtupsize - memtupsize) * sizeof(SortTuple)))
10641071
goto noalloc;
10651072

10661073
/* OK, do it */
10671074
FREEMEM(state, GetMemoryChunkSpace(state->memtuples));
10681075
state->memtupsize = newmemtupsize;
10691076
state->memtuples = (SortTuple *)
1070-
repalloc(state->memtuples,
1071-
state->memtupsize * sizeof(SortTuple));
1077+
repalloc_huge(state->memtuples,
1078+
state->memtupsize * sizeof(SortTuple));
10721079
USEMEM(state, GetMemoryChunkSpace(state->memtuples));
10731080
if (LACKMEM(state))
10741081
elog(ERROR, "unexpected out-of-memory situation during sort");
@@ -1715,7 +1722,7 @@ tuplesort_getdatum(Tuplesortstate *state, bool forward,
17151722
* This is exported for use by the planner. allowedMem is in bytes.
17161723
*/
17171724
int
1718-
tuplesort_merge_order(long allowedMem)
1725+
tuplesort_merge_order(Size allowedMem)
17191726
{
17201727
int mOrder;
17211728

@@ -1749,7 +1756,7 @@ inittapes(Tuplesortstate *state)
17491756
int maxTapes,
17501757
ntuples,
17511758
j;
1752-
long tapeSpace;
1759+
Size tapeSpace;
17531760

17541761
/* Compute number of tapes to use: merge order plus 1 */
17551762
maxTapes = tuplesort_merge_order(state->allowedMem) + 1;
@@ -1798,7 +1805,7 @@ inittapes(Tuplesortstate *state)
17981805
state->mergenext = (int *) palloc0(maxTapes * sizeof(int));
17991806
state->mergelast = (int *) palloc0(maxTapes * sizeof(int));
18001807
state->mergeavailslots = (int *) palloc0(maxTapes * sizeof(int));
1801-
state->mergeavailmem = (long *) palloc0(maxTapes * sizeof(long));
1808+
state->mergeavailmem = (Size *) palloc0(maxTapes * sizeof(Size));
18021809
state->tp_fib = (int *) palloc0(maxTapes * sizeof(int));
18031810
state->tp_runs = (int *) palloc0(maxTapes * sizeof(int));
18041811
state->tp_dummy = (int *) palloc0(maxTapes * sizeof(int));
@@ -2026,7 +2033,7 @@ mergeonerun(Tuplesortstate *state)
20262033
int srcTape;
20272034
int tupIndex;
20282035
SortTuple *tup;
2029-
long priorAvail,
2036+
Size priorAvail,
20302037
spaceFreed;
20312038

20322039
/*
@@ -2100,7 +2107,7 @@ beginmerge(Tuplesortstate *state)
21002107
int tapenum;
21012108
int srcTape;
21022109
int slotsPerTape;
2103-
long spacePerTape;
2110+
Size spacePerTape;
21042111

21052112
/* Heap should be empty here */
21062113
Assert(state->memtupcount == 0);
@@ -2221,7 +2228,7 @@ mergeprereadone(Tuplesortstate *state, int srcTape)
22212228
unsigned int tuplen;
22222229
SortTuple stup;
22232230
int tupIndex;
2224-
long priorAvail,
2231+
Size priorAvail,
22252232
spaceUsed;
22262233

22272234
if (!state->mergeactive[srcTape])

0 commit comments

Comments
 (0)