diff src/backend/utils/sort/tuplesort.c index ce27e40..04a4cbf *** a/src/backend/utils/sort/tuplesort.c --- b/src/backend/utils/sort/tuplesort.c *************** struct Tuplesortstate *** 276,281 **** --- 276,282 ---- SortTuple *memtuples; /* array of SortTuple structs */ int memtupcount; /* number of tuples currently present */ int memtupsize; /* allocated length of memtuples array */ + bool growmemtuples; /* memtuples' growth still underway */ /* * While building initial runs, this is the current output run number *************** tuplesort_begin_common(int workMem, bool *** 570,575 **** --- 571,577 ---- state->memtupcount = 0; state->memtupsize = 1024; /* initial guess */ + state->growmemtuples = true; state->memtuples = (SortTuple *) palloc(state->memtupsize * sizeof(SortTuple)); USEMEM(state, GetMemoryChunkSpace(state->memtuples)); *************** tuplesort_end(Tuplesortstate *state) *** 957,991 **** * Grow the memtuples[] array, if possible within our memory constraint. * Return TRUE if able to enlarge the array, FALSE if not. * ! * At each increment we double the size of the array. When we are short ! * on memory we could consider smaller increases, but because availMem ! * moves around with tuple addition/removal, this might result in thrashing. ! * Small increases in the array size are likely to be pretty inefficient. */ static bool grow_memtuples(Tuplesortstate *state) { /* ! * We need to be sure that we do not cause LACKMEM to become true, else ! * the space management algorithm will go nuts. We assume here that the ! * memory chunk overhead associated with the memtuples array is constant ! * and so there will be no unexpected addition to what we ask for. (The ! * minimum array size established in tuplesort_begin_common is large ! * enough to force palloc to treat it as a separate chunk, so this ! * assumption should be good. But let's check it.) */ if (state->availMem <= (long) (state->memtupsize * sizeof(SortTuple))) ! return false; /* * On a 64-bit machine, allowedMem could be high enough to get us into * trouble with MaxAllocSize, too. */ ! if ((Size) (state->memtupsize * 2) >= MaxAllocSize / sizeof(SortTuple)) ! return false; FREEMEM(state, GetMemoryChunkSpace(state->memtuples)); ! state->memtupsize *= 2; state->memtuples = (SortTuple *) repalloc(state->memtuples, state->memtupsize * sizeof(SortTuple)); --- 959,1050 ---- * Grow the memtuples[] array, if possible within our memory constraint. * Return TRUE if able to enlarge the array, FALSE if not. * ! * At each increment we double the size of the array. When we are short on ! * memory we do attempt one last, smaller increase. This only happens at most ! * once, since availMem moves around with tuple addition/removal. To do othewise ! * might result in thrashing. This is nothing more than a last-ditch effort to ! * avoid exceeding allowedMem, an undesirable outcome if avoidable. */ static bool grow_memtuples(Tuplesortstate *state) { + int newmemtupsize; + int memtupsize = state->memtupsize; + long memNowUsed = state->allowedMem - state->availMem; + + /* This may not be our first time through */ + if (!state->growmemtuples) + return false; + /* ! * We need to be sure that we do not cause LACKMEM to become true, else the ! * space management algorithm will go nuts. ! */ ! if (memNowUsed <= state->availMem) ! { ! newmemtupsize = memtupsize * 2; ! } ! else ! { ! uint64 allowedMem = state->allowedMem; ! ! /* ! * For this last increment, abandon doubling strategy. ! * ! * To make sure LACKMEM doesn't become true, we can't increase ! * memtupsize by more than state->availMem / sizeof(SortTuple) ! * elements. In practice, we want to increase it by considerably less, ! * because we need to leave some space for the tuples to which the new ! * array slots will refer. We assume the new tuples will be about the ! * same size as the tuples we've already seen, and thus use the known ! * size (in bytes) of the tuples seen so far to estimate an appropriate ! * new size for the memtuples array. The optimal value might be higher ! * or lower than we estimate, but it's hard to know that in advance. ! * ! * In any case, we're definitely safe against enlarging the array so ! * much that LACKMEM becomes true, because the memory currently used ! * includes the present array; thus, there would be enough allowedMem ! * for the new array elements even if no other memory were currently ! * used. ! * ! * allowedMem has been converted to uint64 to prevent overflow on ! * platforms where long is only 32 bits wide. Even still, the first ! * argument to Max() below could overflow. ! * ! * XXX: This approach could prevent us from allocating a very large ! * amount of memory that is still within allowedMem in some cases. ! * However, the MaxAllocSize limitation would prevent such an ! * allocation in that situation. ! */ ! state->growmemtuples = false; ! newmemtupsize = Min(Max(memtupsize * allowedMem / memNowUsed, ! memtupsize), ! memtupsize * 2); ! } ! ! /* ! * We assume here that the memory chunk overhead associated with the ! * memtuples array is constant and so there will be no unexpected addition ! * to what we ask for. (The minimum array size established in ! * tuplesort_begin_common is large enough to force palloc to treat it as a ! * separate chunk, so this assumption should be good. But let's check it.) */ if (state->availMem <= (long) (state->memtupsize * sizeof(SortTuple))) ! goto noalloc; /* * On a 64-bit machine, allowedMem could be high enough to get us into * trouble with MaxAllocSize, too. */ ! if ((Size) (newmemtupsize) >= MaxAllocSize / sizeof(SortTuple)) ! goto noalloc; ! ! /* Avoid redundant repalloc */ ! if (newmemtupsize <= memtupsize) ! goto noalloc; FREEMEM(state, GetMemoryChunkSpace(state->memtuples)); ! state->memtupsize = newmemtupsize; state->memtuples = (SortTuple *) repalloc(state->memtuples, state->memtupsize * sizeof(SortTuple)); *************** grow_memtuples(Tuplesortstate *state) *** 993,998 **** --- 1052,1061 ---- if (LACKMEM(state)) elog(ERROR, "unexpected out-of-memory situation during sort"); return true; + + noalloc: + state->growmemtuples = false; + return false; } /* diff src/backend/utils/sort/tuplestore.c index 1b1cf35..743e578 *** a/src/backend/utils/sort/tuplestore.c --- b/src/backend/utils/sort/tuplestore.c *************** tuplestore_puttuple_common(Tuplestoresta *** 632,639 **** if (state->memtupcount >= state->memtupsize - 1) { /* ! * See grow_memtuples() in tuplesort.c for the rationale ! * behind these two tests. */ if (state->availMem > (long) (state->memtupsize * sizeof(void *)) && (Size) (state->memtupsize * 2) < MaxAllocSize / sizeof(void *)) --- 632,640 ---- if (state->memtupcount >= state->memtupsize - 1) { /* ! * See grow_memtuples() in tuplesort.c for the rationale behind ! * these two tests. Note that that module uses a slightly more ! * sophisticated strategy for sizing its memtuples array. */ if (state->availMem > (long) (state->memtupsize * sizeof(void *)) && (Size) (state->memtupsize * 2) < MaxAllocSize / sizeof(void *))