From 7470eb62b45dd5ee5280d8f834a910a64c8bcc85 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Tue, 4 Mar 2025 15:13:22 -0800
Subject: [PATCH v8 4/7] Remove 'additional' pointer from TupleHashEntryData.

Reduces memory required for hash aggregation by avoiding an allocation
and a pointer in the TupleHashEntryData structure. That structure is
used for all buckets, whether occupied or not, so the savings is
substantial.

Discussion: https://postgr.es/m/AApHDvpN4v3t_sdz4dvrv1Fx_ZPw=twSnxuTEytRYP7LFz5K9A@mail.gmail.com
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
---
 src/backend/executor/execGrouping.c | 32 ++++++++++++++++++++++-------
 src/include/executor/executor.h     |  5 ++++-
 src/include/nodes/execnodes.h       |  1 -
 3 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c
index a9d212aaec6..5c06cc6da40 100644
--- a/src/backend/executor/execGrouping.c
+++ b/src/backend/executor/execGrouping.c
@@ -481,15 +481,33 @@ LookupTupleHashEntry_internal(TupleHashTable hashtable, TupleTableSlot *slot,
 		else
 		{
 			/* created new entry */
-			*isnew = true;
+			bool		shouldFree;
+			char	   *mem;
+			MinimalTuple mtup;
 
-			MemoryContextSwitchTo(hashtable->tablecxt);
+			*isnew = true;
 
-			entry->firstTuple = ExecCopySlotMinimalTuple(slot);
-			if (hashtable->additionalsize > 0)
-				entry->additional = palloc0(hashtable->additionalsize);
-			else
-				entry->additional = NULL;
+			MemoryContextSwitchTo(hashtable->tempcxt);
+
+			/* ignore shouldFree, because we're in the temp context anyway */
+			mtup = ExecFetchSlotMinimalTuple(slot, &shouldFree);
+
+			/*
+			 * Allocate enough memory in the tablecxt for the additionalsize
+			 * followed by the tuple. Clear the additional data, and copy the
+			 * tuple.
+			 *
+			 * The caller can get a pointer to the additional data with
+			 * TupleHashEntryGetAdditional(), and store arbitrary data there.
+			 * Placing both the tuple and additional data in the same
+			 * allocation avoids the need to store an extra pointer in
+			 * TupleHashEntryData or allocate an additional chunk.
+			 */
+			mem = MemoryContextAlloc(hashtable->tablecxt,
+									 mtup->t_len + hashtable->additionalsize);
+			memset(mem, 0, hashtable->additionalsize);
+			entry->firstTuple = (MinimalTuple) (mem + hashtable->additionalsize);
+			memcpy(entry->firstTuple, mtup, mtup->t_len);
 		}
 	}
 	else
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d0aa0e94241..90fa466dea4 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -188,7 +188,10 @@ TupleHashEntryGetTuple(TupleHashEntry entry)
 static inline void *
 TupleHashEntryGetAdditional(TupleHashTable hashtable, TupleHashEntry entry)
 {
-	return entry->additional;
+	if (hashtable->additionalsize > 0)
+		return (char *) entry->firstTuple - hashtable->additionalsize;
+	else
+		return NULL;
 }
 #endif
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b9ca5f7f5fa..e93533dbbc5 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -840,7 +840,6 @@ typedef struct TupleHashTableData *TupleHashTable;
 typedef struct TupleHashEntryData
 {
 	MinimalTuple firstTuple;	/* copy of first tuple in this group */
-	void	   *additional;		/* user data */
 	uint32		status;			/* hash status */
 	uint32		hash;			/* hash value (cached) */
 } TupleHashEntryData;
-- 
2.34.1

