From 286310e6a52af7883d20899163aaa349b479d614 Mon Sep 17 00:00:00 2001
From: Amit Khandekar <amitdkhan.pg@gmail.com>
Date: Wed, 9 Sep 2020 11:52:59 +0800
Subject: [PATCH 1/2] Avoid redundant tuple copy while sending tuples to Gather

If the tuple to be sent is a heap tuple, get the pointer to the minimal
tuple data from the heap tuple, and use this as the source data for
shm_mq_send(). This allows us to prevent a tuple copy when the slot
is a heap tuple slot.

Device a new function ExecFetchSlotMinimalTupleData() that tries to
send an in-place minimal tuple data or returns a copy only if the
slot is something other than a heap tuple or minimal tuple slot.

This shows between 4 to 10% speed up for simple non-aggregate parallel
queries.
---
 src/backend/executor/execTuples.c | 59 +++++++++++++++++++++++++++++++
 src/backend/executor/tqueue.c     | 17 +++++----
 src/include/executor/tuptable.h   |  2 ++
 3 files changed, 71 insertions(+), 7 deletions(-)

diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 4c90ac5236..55a6652b4e 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -1682,6 +1682,65 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot,
 	}
 }
 
+/* --------------------------------
+ *		ExecFetchSlotMinimalTupleData
+ *			Return minimal tuple data, or the data belonging to minimal tuple
+ *			section if it's a heap tuple slot.
+ *
+ *		This function is similar to ExecFetchSlotMinimalTuple(), but it goes a
+ *		step further in trying to avoid redundant tuple copy. It does this by
+ *		returning the in-place minimal tuple data region if it's a heap tuple.
+ *		If the slot is neither a minimal tuple nor heap tuple slot, a minimal
+ *		tuple copy is returned, and should_free is set to true. Callers can use
+ *		this if they only want the underlying minimal tuple data.
+ *		One use is where minimal tuple data is sent through the gather queues.
+ *		The receiver end can then treat the data as a MinimalTupleData, but
+ *		they should update it's t_len field, because the data might have
+ *		originally belonged to a heap tuple rather than minimal tuple.
+ */
+char *
+ExecFetchSlotMinimalTupleData(TupleTableSlot *slot, uint32 *len,
+							  bool *shouldFree)
+{
+	/*
+	 * sanity checks
+	 */
+	Assert(slot != NULL);
+	Assert(!TTS_EMPTY(slot));
+
+	if (slot->tts_ops->get_heap_tuple)
+	{
+		HeapTuple	htuple;
+
+		if (shouldFree)
+			*shouldFree = false;
+		htuple = slot->tts_ops->get_heap_tuple(slot);
+		*len = htuple->t_len - MINIMAL_TUPLE_OFFSET;
+
+		return (char*) htuple->t_data + MINIMAL_TUPLE_OFFSET;
+	}
+	else
+	{
+		MinimalTuple	mtuple;
+
+		if (slot->tts_ops->get_minimal_tuple)
+		{
+			if (shouldFree)
+				*shouldFree = false;
+			mtuple = slot->tts_ops->get_minimal_tuple(slot);
+		}
+		else
+		{
+			if (shouldFree)
+				*shouldFree = true;
+			mtuple = slot->tts_ops->copy_minimal_tuple(slot);
+		}
+		*len = mtuple->t_len;
+
+		return (char *) mtuple;
+	}
+}
+
 /* --------------------------------
  *		ExecFetchSlotHeapTupleDatum
  *			Fetch the slot's tuple as a composite-type Datum.
diff --git a/src/backend/executor/tqueue.c b/src/backend/executor/tqueue.c
index 30a264ebea..c70ee0f39a 100644
--- a/src/backend/executor/tqueue.c
+++ b/src/backend/executor/tqueue.c
@@ -54,16 +54,17 @@ static bool
 tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 {
 	TQueueDestReceiver *tqueue = (TQueueDestReceiver *) self;
-	MinimalTuple tuple;
+	char	   *minimal_tuple_data;
+	uint32		len;
 	shm_mq_result result;
 	bool		should_free;
 
-	/* Send the tuple itself. */
-	tuple = ExecFetchSlotMinimalTuple(slot, &should_free);
-	result = shm_mq_send(tqueue->queue, tuple->t_len, tuple, false);
+	/* Send the minimal tuple data. */
+	minimal_tuple_data = ExecFetchSlotMinimalTupleData(slot, &len, &should_free);
+	result = shm_mq_send(tqueue->queue, len, minimal_tuple_data, false);
 
 	if (should_free)
-		pfree(tuple);
+		pfree(minimal_tuple_data);
 
 	/* Check for failure. */
 	if (result == SHM_MQ_DETACHED)
@@ -201,10 +202,12 @@ TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done)
 
 	/*
 	 * Return a pointer to the queue memory directly (which had better be
-	 * sufficiently aligned).
+	 * sufficiently aligned). Also, the sender might not have updated the t_len
+	 * if the data belonged to a heap tuple rather than a minimal tuple. So
+	 * update it now.
 	 */
 	tuple = (MinimalTuple) data;
-	Assert(tuple->t_len == nbytes);
+	tuple->t_len = nbytes;
 
 	return tuple;
 }
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index f7df70b5ab..da78929035 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -325,6 +325,8 @@ extern void ExecStoreHeapTupleDatum(Datum data, TupleTableSlot *slot);
 extern HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot,
 											  bool *shouldFree);
+extern char *ExecFetchSlotMinimalTupleData(TupleTableSlot *slot, uint32 *len,
+										   bool *shouldFree);
 extern Datum ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot);
 extern void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum,
 								 int lastAttNum);
-- 
2.17.1

