Attached is a small patch that introduces a simple function,
AllocSetEstimateChunkSpace(), and uses it to improve the estimate for
the size of a hash entry for hash aggregation.
Getting reasonable estimates for the hash entry size (including the
TupleHashEntryData, the group key tuple, the per-group state, and by-
ref transition values) is important for multiple reasons:
* It helps the planner know when hash aggregation is likely to spill,
and how to cost it.
* It helps hash aggregation regulate itself by setting a group limit
(separate from the memory limit) to account for growing by-ref
transition values.
* It helps choose a good initial size for the hash table. Too large of
a hash table will crowd out space that could be used for the group keys
or the per-group state.
Each group requires up to three palloc chunks: one for the group key
tuple, one for the per-group states, and one for a by-ref transition
value. Each chunk can have a lot of overhead: in addition to the chunk
header (16 bytes overhead), it also rounds the value up to a power of
two (~25% overhead). So, it's important to account for this chunk
overhead.
I considered making it a method of a memory context, but the planner
will call this function before the hash agg memory context is created.
It seems to make more sense for this to just be an AllocSet-specific
function for now.
Regards,
Jeff Davis
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 2d94d7dc25e..7fa71ad2d65 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1637,16 +1637,37 @@ find_hash_columns(AggState *aggstate)
/*
* Estimate per-hash-table-entry overhead.
+ *
+ * NB: This assumes the memory context at execution time will be an
+ * AllocSetContext.
*/
Size
-hash_agg_entry_size(int numAggs, Size tupleWidth, Size transitionSpace)
+hash_agg_entry_size(int numTrans, Size tupleWidth, Size transitionSpace)
{
+ Size tupleChunkSize;
+ Size pergroupChunkSize;
+ Size transitionChunkSize;
+ Size tupleSize = (MAXALIGN(SizeofMinimalTupleHeader) +
+ tupleWidth);
+ Size pergroupSize = numTrans * sizeof(AggStatePerGroupData);
+
+ tupleChunkSize = AllocSetEstimateChunkSpace(tupleSize);
+
+ if (pergroupSize > 0)
+ pergroupChunkSize = AllocSetEstimateChunkSpace(pergroupSize);
+ else
+ pergroupChunkSize = 0;
+
+ if (transitionSpace > 0)
+ transitionChunkSize = AllocSetEstimateChunkSpace(transitionSpace);
+ else
+ transitionChunkSize = 0;
+
return
- MAXALIGN(SizeofMinimalTupleHeader) +
- MAXALIGN(tupleWidth) +
- MAXALIGN(sizeof(TupleHashEntryData) +
- numAggs * sizeof(AggStatePerGroupData)) +
- transitionSpace;
+ sizeof(TupleHashEntryData) +
+ tupleChunkSize +
+ pergroupChunkSize +
+ transitionChunkSize;
}
/*
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index c0623f106d2..678ddd77912 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -560,6 +560,30 @@ AllocSetContextCreateInternal(MemoryContext parent,
return (MemoryContext) set;
}
+/*
+ * Estimate total memory consumed for a chunk of the requested size.
+ */
+Size
+AllocSetEstimateChunkSpace(Size size)
+{
+ Size chunk_size;
+
+ if (size > ALLOC_CHUNK_LIMIT)
+ {
+ chunk_size = MAXALIGN(size);
+
+ return chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
+ }
+ else
+ {
+ int fidx = AllocSetFreeIndex(size);
+
+ chunk_size = (1 << ALLOC_MINBITS) << fidx;
+
+ return chunk_size + ALLOC_CHUNKHDRSZ;
+ }
+}
+
/*
* AllocSetReset
* Frees all memory which is allocated in the given set.
diff --git a/src/include/executor/nodeAgg.h b/src/include/executor/nodeAgg.h
index a5b8a004d1e..c2b55728bfa 100644
--- a/src/include/executor/nodeAgg.h
+++ b/src/include/executor/nodeAgg.h
@@ -314,7 +314,7 @@ extern AggState *ExecInitAgg(Agg *node, EState *estate, int eflags);
extern void ExecEndAgg(AggState *node);
extern void ExecReScanAgg(AggState *node);
-extern Size hash_agg_entry_size(int numAggs, Size tupleWidth,
+extern Size hash_agg_entry_size(int numTrans, Size tupleWidth,
Size transitionSpace);
extern void hash_agg_set_limits(double hashentrysize, uint64 input_groups,
int used_bits, Size *mem_limit,
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 909bc2e9888..67e53e4b977 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -155,6 +155,7 @@ extern MemoryContext AllocSetContextCreateInternal(MemoryContext parent,
Size minContextSize,
Size initBlockSize,
Size maxBlockSize);
+extern Size AllocSetEstimateChunkSpace(Size size);
/*
* This wrapper macro exists to check for non-constant strings used as context