mirror of
https://github.com/postgres/postgres.git
synced 2025-05-08 07:21:33 +03:00
Use bump memory context for tuplesorts
29f6a959c added a bump allocator type for efficient compact allocations. Here we make use of this for non-bounded tuplesorts to store tuples. This is very space efficient when storing narrow tuples due to bump.c not having chunk headers. This means we can fit more tuples in work_mem before spilling to disk, or perform an in-memory sort touching fewer cacheline. Author: David Rowley Reviewed-by: Nathan Bossart Reviewed-by: Matthias van de Meent Reviewed-by: Tomas Vondra Reviewed-by: John Naylor Discussion: https://postgr.es/m/CAApHDvqGSpCU95TmM=Bp=6xjL_nLys4zdZOpfNyWBk97Xrdj2w@mail.gmail.com
This commit is contained in:
parent
f3ff7bf83b
commit
6ed83d5fa5
@ -191,6 +191,11 @@ struct Tuplesortstate
|
|||||||
* tuples to return? */
|
* tuples to return? */
|
||||||
bool boundUsed; /* true if we made use of a bounded heap */
|
bool boundUsed; /* true if we made use of a bounded heap */
|
||||||
int bound; /* if bounded, the maximum number of tuples */
|
int bound; /* if bounded, the maximum number of tuples */
|
||||||
|
int64 tupleMem; /* memory consumed by individual tuples.
|
||||||
|
* storing this separately from what we track
|
||||||
|
* in availMem allows us to subtract the
|
||||||
|
* memory consumed by all tuples when dumping
|
||||||
|
* tuples to tape */
|
||||||
int64 availMem; /* remaining memory available, in bytes */
|
int64 availMem; /* remaining memory available, in bytes */
|
||||||
int64 allowedMem; /* total memory allowed, in bytes */
|
int64 allowedMem; /* total memory allowed, in bytes */
|
||||||
int maxTapes; /* max number of input tapes to merge in each
|
int maxTapes; /* max number of input tapes to merge in each
|
||||||
@ -764,16 +769,16 @@ tuplesort_begin_batch(Tuplesortstate *state)
|
|||||||
* in the parent context, not this context, because there is no need to
|
* in the parent context, not this context, because there is no need to
|
||||||
* free memtuples early. For bounded sorts, tuples may be pfreed in any
|
* free memtuples early. For bounded sorts, tuples may be pfreed in any
|
||||||
* order, so we use a regular aset.c context so that it can make use of
|
* order, so we use a regular aset.c context so that it can make use of
|
||||||
* free'd memory. When the sort is not bounded, we make use of a
|
* free'd memory. When the sort is not bounded, we make use of a bump.c
|
||||||
* generation.c context as this keeps allocations more compact with less
|
* context as this keeps allocations more compact with less wastage.
|
||||||
* wastage. Allocations are also slightly more CPU efficient.
|
* Allocations are also slightly more CPU efficient.
|
||||||
*/
|
*/
|
||||||
if (state->base.sortopt & TUPLESORT_ALLOWBOUNDED)
|
if (TupleSortUseBumpTupleCxt(state->base.sortopt))
|
||||||
state->base.tuplecontext = AllocSetContextCreate(state->base.sortcontext,
|
state->base.tuplecontext = BumpContextCreate(state->base.sortcontext,
|
||||||
"Caller tuples",
|
"Caller tuples",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
else
|
else
|
||||||
state->base.tuplecontext = GenerationContextCreate(state->base.sortcontext,
|
state->base.tuplecontext = AllocSetContextCreate(state->base.sortcontext,
|
||||||
"Caller tuples",
|
"Caller tuples",
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
ALLOCSET_DEFAULT_SIZES);
|
||||||
|
|
||||||
@ -1181,15 +1186,16 @@ noalloc:
|
|||||||
* Shared code for tuple and datum cases.
|
* Shared code for tuple and datum cases.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
tuplesort_puttuple_common(Tuplesortstate *state, SortTuple *tuple, bool useAbbrev)
|
tuplesort_puttuple_common(Tuplesortstate *state, SortTuple *tuple,
|
||||||
|
bool useAbbrev, Size tuplen)
|
||||||
{
|
{
|
||||||
MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext);
|
MemoryContext oldcontext = MemoryContextSwitchTo(state->base.sortcontext);
|
||||||
|
|
||||||
Assert(!LEADER(state));
|
Assert(!LEADER(state));
|
||||||
|
|
||||||
/* Count the size of the out-of-line data */
|
/* account for the memory used for this tuple */
|
||||||
if (tuple->tuple != NULL)
|
USEMEM(state, tuplen);
|
||||||
USEMEM(state, GetMemoryChunkSpace(tuple->tuple));
|
state->tupleMem += tuplen;
|
||||||
|
|
||||||
if (!useAbbrev)
|
if (!useAbbrev)
|
||||||
{
|
{
|
||||||
@ -2397,13 +2403,6 @@ dumptuples(Tuplesortstate *state, bool alltuples)
|
|||||||
SortTuple *stup = &state->memtuples[i];
|
SortTuple *stup = &state->memtuples[i];
|
||||||
|
|
||||||
WRITETUP(state, state->destTape, stup);
|
WRITETUP(state, state->destTape, stup);
|
||||||
|
|
||||||
/*
|
|
||||||
* Account for freeing the tuple, but no need to do the actual pfree
|
|
||||||
* since the tuplecontext is being reset after the loop.
|
|
||||||
*/
|
|
||||||
if (stup->tuple != NULL)
|
|
||||||
FREEMEM(state, GetMemoryChunkSpace(stup->tuple));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state->memtupcount = 0;
|
state->memtupcount = 0;
|
||||||
@ -2411,12 +2410,19 @@ dumptuples(Tuplesortstate *state, bool alltuples)
|
|||||||
/*
|
/*
|
||||||
* Reset tuple memory. We've freed all of the tuples that we previously
|
* Reset tuple memory. We've freed all of the tuples that we previously
|
||||||
* allocated. It's important to avoid fragmentation when there is a stark
|
* allocated. It's important to avoid fragmentation when there is a stark
|
||||||
* change in the sizes of incoming tuples. Fragmentation due to
|
* change in the sizes of incoming tuples. In bounded sorts,
|
||||||
* AllocSetFree's bucketing by size class might be particularly bad if
|
* fragmentation due to AllocSetFree's bucketing by size class might be
|
||||||
* this step wasn't taken.
|
* particularly bad if this step wasn't taken.
|
||||||
*/
|
*/
|
||||||
MemoryContextReset(state->base.tuplecontext);
|
MemoryContextReset(state->base.tuplecontext);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now update the memory accounting to subtract the memory used by the
|
||||||
|
* tuple.
|
||||||
|
*/
|
||||||
|
FREEMEM(state, state->tupleMem);
|
||||||
|
state->tupleMem = 0;
|
||||||
|
|
||||||
markrunend(state->destTape);
|
markrunend(state->destTape);
|
||||||
|
|
||||||
#ifdef TRACE_SORT
|
#ifdef TRACE_SORT
|
||||||
|
@ -674,6 +674,7 @@ tuplesort_puttupleslot(Tuplesortstate *state, TupleTableSlot *slot)
|
|||||||
SortTuple stup;
|
SortTuple stup;
|
||||||
MinimalTuple tuple;
|
MinimalTuple tuple;
|
||||||
HeapTupleData htup;
|
HeapTupleData htup;
|
||||||
|
Size tuplen;
|
||||||
|
|
||||||
/* copy the tuple into sort storage */
|
/* copy the tuple into sort storage */
|
||||||
tuple = ExecCopySlotMinimalTuple(slot);
|
tuple = ExecCopySlotMinimalTuple(slot);
|
||||||
@ -686,9 +687,15 @@ tuplesort_puttupleslot(Tuplesortstate *state, TupleTableSlot *slot)
|
|||||||
tupDesc,
|
tupDesc,
|
||||||
&stup.isnull1);
|
&stup.isnull1);
|
||||||
|
|
||||||
|
/* GetMemoryChunkSpace is not supported for bump contexts */
|
||||||
|
if (TupleSortUseBumpTupleCxt(base->sortopt))
|
||||||
|
tuplen = MAXALIGN(tuple->t_len);
|
||||||
|
else
|
||||||
|
tuplen = GetMemoryChunkSpace(tuple);
|
||||||
|
|
||||||
tuplesort_puttuple_common(state, &stup,
|
tuplesort_puttuple_common(state, &stup,
|
||||||
base->sortKeys->abbrev_converter &&
|
base->sortKeys->abbrev_converter &&
|
||||||
!stup.isnull1);
|
!stup.isnull1, tuplen);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
MemoryContextSwitchTo(oldcontext);
|
||||||
}
|
}
|
||||||
@ -705,6 +712,7 @@ tuplesort_putheaptuple(Tuplesortstate *state, HeapTuple tup)
|
|||||||
TuplesortPublic *base = TuplesortstateGetPublic(state);
|
TuplesortPublic *base = TuplesortstateGetPublic(state);
|
||||||
MemoryContext oldcontext = MemoryContextSwitchTo(base->tuplecontext);
|
MemoryContext oldcontext = MemoryContextSwitchTo(base->tuplecontext);
|
||||||
TuplesortClusterArg *arg = (TuplesortClusterArg *) base->arg;
|
TuplesortClusterArg *arg = (TuplesortClusterArg *) base->arg;
|
||||||
|
Size tuplen;
|
||||||
|
|
||||||
/* copy the tuple into sort storage */
|
/* copy the tuple into sort storage */
|
||||||
tup = heap_copytuple(tup);
|
tup = heap_copytuple(tup);
|
||||||
@ -722,10 +730,16 @@ tuplesort_putheaptuple(Tuplesortstate *state, HeapTuple tup)
|
|||||||
&stup.isnull1);
|
&stup.isnull1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* GetMemoryChunkSpace is not supported for bump contexts */
|
||||||
|
if (TupleSortUseBumpTupleCxt(base->sortopt))
|
||||||
|
tuplen = MAXALIGN(HEAPTUPLESIZE + tup->t_len);
|
||||||
|
else
|
||||||
|
tuplen = GetMemoryChunkSpace(tup);
|
||||||
|
|
||||||
tuplesort_puttuple_common(state, &stup,
|
tuplesort_puttuple_common(state, &stup,
|
||||||
base->haveDatum1 &&
|
base->haveDatum1 &&
|
||||||
base->sortKeys->abbrev_converter &&
|
base->sortKeys->abbrev_converter &&
|
||||||
!stup.isnull1);
|
!stup.isnull1, tuplen);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
MemoryContextSwitchTo(oldcontext);
|
||||||
}
|
}
|
||||||
@ -743,6 +757,7 @@ tuplesort_putindextuplevalues(Tuplesortstate *state, Relation rel,
|
|||||||
IndexTuple tuple;
|
IndexTuple tuple;
|
||||||
TuplesortPublic *base = TuplesortstateGetPublic(state);
|
TuplesortPublic *base = TuplesortstateGetPublic(state);
|
||||||
TuplesortIndexArg *arg = (TuplesortIndexArg *) base->arg;
|
TuplesortIndexArg *arg = (TuplesortIndexArg *) base->arg;
|
||||||
|
Size tuplen;
|
||||||
|
|
||||||
stup.tuple = index_form_tuple_context(RelationGetDescr(rel), values,
|
stup.tuple = index_form_tuple_context(RelationGetDescr(rel), values,
|
||||||
isnull, base->tuplecontext);
|
isnull, base->tuplecontext);
|
||||||
@ -754,10 +769,16 @@ tuplesort_putindextuplevalues(Tuplesortstate *state, Relation rel,
|
|||||||
RelationGetDescr(arg->indexRel),
|
RelationGetDescr(arg->indexRel),
|
||||||
&stup.isnull1);
|
&stup.isnull1);
|
||||||
|
|
||||||
|
/* GetMemoryChunkSpace is not supported for bump contexts */
|
||||||
|
if (TupleSortUseBumpTupleCxt(base->sortopt))
|
||||||
|
tuplen = MAXALIGN(tuple->t_info & INDEX_SIZE_MASK);
|
||||||
|
else
|
||||||
|
tuplen = GetMemoryChunkSpace(tuple);
|
||||||
|
|
||||||
tuplesort_puttuple_common(state, &stup,
|
tuplesort_puttuple_common(state, &stup,
|
||||||
base->sortKeys &&
|
base->sortKeys &&
|
||||||
base->sortKeys->abbrev_converter &&
|
base->sortKeys->abbrev_converter &&
|
||||||
!stup.isnull1);
|
!stup.isnull1, tuplen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -770,6 +791,7 @@ tuplesort_putbrintuple(Tuplesortstate *state, BrinTuple *tuple, Size size)
|
|||||||
BrinSortTuple *bstup;
|
BrinSortTuple *bstup;
|
||||||
TuplesortPublic *base = TuplesortstateGetPublic(state);
|
TuplesortPublic *base = TuplesortstateGetPublic(state);
|
||||||
MemoryContext oldcontext = MemoryContextSwitchTo(base->tuplecontext);
|
MemoryContext oldcontext = MemoryContextSwitchTo(base->tuplecontext);
|
||||||
|
Size tuplen;
|
||||||
|
|
||||||
/* allocate space for the whole BRIN sort tuple */
|
/* allocate space for the whole BRIN sort tuple */
|
||||||
bstup = palloc(BRINSORTTUPLE_SIZE(size));
|
bstup = palloc(BRINSORTTUPLE_SIZE(size));
|
||||||
@ -781,10 +803,16 @@ tuplesort_putbrintuple(Tuplesortstate *state, BrinTuple *tuple, Size size)
|
|||||||
stup.datum1 = tuple->bt_blkno;
|
stup.datum1 = tuple->bt_blkno;
|
||||||
stup.isnull1 = false;
|
stup.isnull1 = false;
|
||||||
|
|
||||||
|
/* GetMemoryChunkSpace is not supported for bump contexts */
|
||||||
|
if (TupleSortUseBumpTupleCxt(base->sortopt))
|
||||||
|
tuplen = MAXALIGN(BRINSORTTUPLE_SIZE(size));
|
||||||
|
else
|
||||||
|
tuplen = GetMemoryChunkSpace(bstup);
|
||||||
|
|
||||||
tuplesort_puttuple_common(state, &stup,
|
tuplesort_puttuple_common(state, &stup,
|
||||||
base->sortKeys &&
|
base->sortKeys &&
|
||||||
base->sortKeys->abbrev_converter &&
|
base->sortKeys->abbrev_converter &&
|
||||||
!stup.isnull1);
|
!stup.isnull1, tuplen);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
MemoryContextSwitchTo(oldcontext);
|
||||||
}
|
}
|
||||||
@ -833,7 +861,7 @@ tuplesort_putdatum(Tuplesortstate *state, Datum val, bool isNull)
|
|||||||
|
|
||||||
tuplesort_puttuple_common(state, &stup,
|
tuplesort_puttuple_common(state, &stup,
|
||||||
base->tuples &&
|
base->tuples &&
|
||||||
base->sortKeys->abbrev_converter && !isNull);
|
base->sortKeys->abbrev_converter && !isNull, 0);
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
MemoryContextSwitchTo(oldcontext);
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,15 @@ typedef enum
|
|||||||
/* specifies if the tuplesort is able to support bounded sorts */
|
/* specifies if the tuplesort is able to support bounded sorts */
|
||||||
#define TUPLESORT_ALLOWBOUNDED (1 << 1)
|
#define TUPLESORT_ALLOWBOUNDED (1 << 1)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For bounded sort, tuples get pfree'd when they fall outside of the bound.
|
||||||
|
* When bounded sorts are not required, we can use a bump context for tuple
|
||||||
|
* allocation as there's no risk that pfree will ever be called for a tuple.
|
||||||
|
* Define a macro to make it easier for code to figure out if we're using a
|
||||||
|
* bump allocator.
|
||||||
|
*/
|
||||||
|
#define TupleSortUseBumpTupleCxt(opt) (((opt) & TUPLESORT_ALLOWBOUNDED) == 0)
|
||||||
|
|
||||||
typedef struct TuplesortInstrumentation
|
typedef struct TuplesortInstrumentation
|
||||||
{
|
{
|
||||||
TuplesortMethod sortMethod; /* sort algorithm used */
|
TuplesortMethod sortMethod; /* sort algorithm used */
|
||||||
@ -109,10 +118,11 @@ typedef struct TuplesortInstrumentation
|
|||||||
* The objects we actually sort are SortTuple structs. These contain
|
* The objects we actually sort are SortTuple structs. These contain
|
||||||
* a pointer to the tuple proper (might be a MinimalTuple or IndexTuple),
|
* a pointer to the tuple proper (might be a MinimalTuple or IndexTuple),
|
||||||
* which is a separate palloc chunk --- we assume it is just one chunk and
|
* which is a separate palloc chunk --- we assume it is just one chunk and
|
||||||
* can be freed by a simple pfree() (except during merge, when we use a
|
* can be freed by a simple pfree() (except during merge, where we use a
|
||||||
* simple slab allocator). SortTuples also contain the tuple's first key
|
* simple slab allocator, and during a non-bounded sort where we use a bump
|
||||||
* column in Datum/nullflag format, and a source/input tape number that
|
* allocator). SortTuples also contain the tuple's first key column in
|
||||||
* tracks which tape each heap element/slot belongs to during merging.
|
* Datum/nullflag format, and a source/input tape number that tracks which
|
||||||
|
* tape each heap element/slot belongs to during merging.
|
||||||
*
|
*
|
||||||
* Storing the first key column lets us save heap_getattr or index_getattr
|
* Storing the first key column lets us save heap_getattr or index_getattr
|
||||||
* calls during tuple comparisons. We could extract and save all the key
|
* calls during tuple comparisons. We could extract and save all the key
|
||||||
@ -367,7 +377,8 @@ extern Tuplesortstate *tuplesort_begin_common(int workMem,
|
|||||||
extern void tuplesort_set_bound(Tuplesortstate *state, int64 bound);
|
extern void tuplesort_set_bound(Tuplesortstate *state, int64 bound);
|
||||||
extern bool tuplesort_used_bound(Tuplesortstate *state);
|
extern bool tuplesort_used_bound(Tuplesortstate *state);
|
||||||
extern void tuplesort_puttuple_common(Tuplesortstate *state,
|
extern void tuplesort_puttuple_common(Tuplesortstate *state,
|
||||||
SortTuple *tuple, bool useAbbrev);
|
SortTuple *tuple, bool useAbbrev,
|
||||||
|
Size tuplen);
|
||||||
extern void tuplesort_performsort(Tuplesortstate *state);
|
extern void tuplesort_performsort(Tuplesortstate *state);
|
||||||
extern bool tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
|
extern bool tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
|
||||||
SortTuple *stup);
|
SortTuple *stup);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user