From 25a22cfc0f466219c875bc73d5110a538bf8b96c Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 3 Oct 2017 00:39:16 -0700
Subject: [PATCH 5/6] Improve performance of SendRowDescriptionMessage.

There's three categories of changes leading to better performance:
- Splitting the per-attribute part of SendRowDescriptionMessage into a
  v2 and a v3 version allows avoiding branches for every attribute.
- Preallocating the size of the buffer to be big enough for all
  attributes and then using pq_write* avoids unnecessary buffer
  size checks & resizing.
- Reusing a persistently allocated StringInfo for all
  SendRowDescriptionMessage() invocations avoids repeated allocations
  & reallocations.

Author: Andres Freund
Discussion: https://postgr.es/m/20170914063418.sckdzgjfrsbekae4@alap3.anarazel.de
---
 src/backend/access/common/printtup.c | 153 +++++++++++++++++++++++++----------
 src/backend/tcop/postgres.c          |  34 ++++++--
 src/include/access/printtup.h        |   4 +-
 3 files changed, 141 insertions(+), 50 deletions(-)

diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index bb807239b5a..abe0426a23b 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -32,6 +32,8 @@ static bool printtup_internal_20(TupleTableSlot *slot, DestReceiver *self);
 static void printtup_shutdown(DestReceiver *self);
 static void printtup_destroy(DestReceiver *self);
 
+static void SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats);
+static void SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats);
 
 /* ----------------------------------------------------------------
  *		printtup / debugtup support
@@ -128,6 +130,9 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	DR_printtup *myState = (DR_printtup *) self;
 	Portal		portal = myState->portal;
 
+	/* create buffer to be used for all messages */
+	initStringInfo(&myState->buf);
+
 	/*
 	 * Create a temporary memory context that we can reset once per row to
 	 * recover palloc'd memory.  This avoids any problems with leaks inside
@@ -158,7 +163,8 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 * descriptor of the tuples.
 	 */
 	if (myState->sendDescrip)
-		SendRowDescriptionMessage(typeinfo,
+		SendRowDescriptionMessage(&myState->buf,
+								  typeinfo,
 								  FetchPortalTargetList(portal),
 								  portal->formats);
 
@@ -186,16 +192,109 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
  * send zeroes for the format codes in that case.
  */
 void
-SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
+SendRowDescriptionMessage(StringInfo buf, TupleDesc typeinfo,
+						  List *targetlist, int16 *formats)
 {
 	int			natts = typeinfo->natts;
 	int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
+
+	/* tuple descriptor message type */
+	pq_beginmessage_reuse(buf, 'T');
+	/* # of attrs in tuples */
+	pq_sendint16(buf, natts);
+
+	if (proto >= 3)
+		SendRowDescriptionCols_3(buf, typeinfo, targetlist, formats);
+	else
+		SendRowDescriptionCols_2(buf, typeinfo, targetlist, formats);
+
+	pq_endmessage_reuse(buf);
+}
+
+/*
+ * Send description for each column when using v3+ protocol
+ */
+static void
+SendRowDescriptionCols_3(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats)
+{
+	int			natts = typeinfo->natts;
 	int			i;
-	StringInfoData buf;
 	ListCell   *tlist_item = list_head(targetlist);
 
-	pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
-	pq_sendint(&buf, natts, 2); /* # of attrs in tuples */
+	/*
+	 * Preallocate memory for the entire message to be sent. That allows to
+	 * use the significantly faster inline pqformat.h functions and to avoid
+	 * reallocations.
+	 *
+	 * Have to overestimate the size of the column-names, to account for
+	 * character set overhead.
+	 */
+	enlargeStringInfo(buf, (NAMEDATALEN * MAX_CONVERSION_GROWTH /* attname */
+							+ sizeof(int32) /* attlen */
+							+ sizeof(int32) /* resorigtbl */
+							+ sizeof(int16) /* resorigcol */
+							+ sizeof(Oid) /* atttypid */
+							+ sizeof(int16) /* attlen */
+							+ sizeof(int32) /* attypmod */
+						  ) * natts);
+
+	for (i = 0; i < natts; ++i)
+	{
+		Form_pg_attribute att = TupleDescAttr(typeinfo, i);
+		Oid			atttypid = att->atttypid;
+		int32		atttypmod = att->atttypmod;
+		int32		resorigtbl;
+		int32		resorigcol;
+		int16		format;
+
+		/*
+		 * If column is a domain, send the base type and typmod
+		 * instead. Lookup before sending any ints, for efficiency.
+		 */
+		atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod);
+
+		/* Do we have a non-resjunk tlist item? */
+		while (tlist_item &&
+			   ((TargetEntry *) lfirst(tlist_item))->resjunk)
+			tlist_item = lnext(tlist_item);
+		if (tlist_item)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(tlist_item);
+
+			resorigtbl = tle->resorigtbl;
+			resorigcol = tle->resorigcol;
+			tlist_item = lnext(tlist_item);
+		}
+		else
+		{
+			/* No info available, so send zeroes */
+			resorigtbl = 0;
+			resorigcol = 0;
+		}
+
+		if (formats)
+			format = formats[i];
+		else
+			format = 0;
+
+		pq_writestring(buf, NameStr(att->attname));
+		pq_writeint32(buf, resorigtbl);
+		pq_writeint16(buf, resorigcol);
+		pq_writeint32(buf, atttypid);
+		pq_writeint16(buf, att->attlen);
+		pq_writeint32(buf, atttypmod);
+		pq_writeint16(buf, format);
+	}
+}
+
+/*
+ * Send description for each column when using v2 protocol
+ */
+static void
+SendRowDescriptionCols_2(StringInfo buf, TupleDesc typeinfo, List *targetlist, int16 *formats)
+{
+	int			natts = typeinfo->natts;
+	int			i;
 
 	for (i = 0; i < natts; ++i)
 	{
@@ -203,44 +302,16 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
 		Oid			atttypid = att->atttypid;
 		int32		atttypmod = att->atttypmod;
 
-		pq_sendstring(&buf, NameStr(att->attname));
-		/* column ID info appears in protocol 3.0 and up */
-		if (proto >= 3)
-		{
-			/* Do we have a non-resjunk tlist item? */
-			while (tlist_item &&
-				   ((TargetEntry *) lfirst(tlist_item))->resjunk)
-				tlist_item = lnext(tlist_item);
-			if (tlist_item)
-			{
-				TargetEntry *tle = (TargetEntry *) lfirst(tlist_item);
-
-				pq_sendint(&buf, tle->resorigtbl, 4);
-				pq_sendint(&buf, tle->resorigcol, 2);
-				tlist_item = lnext(tlist_item);
-			}
-			else
-			{
-				/* No info available, so send zeroes */
-				pq_sendint(&buf, 0, 4);
-				pq_sendint(&buf, 0, 2);
-			}
-		}
 		/* If column is a domain, send the base type and typmod instead */
 		atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod);
-		pq_sendint(&buf, (int) atttypid, sizeof(atttypid));
-		pq_sendint(&buf, att->attlen, sizeof(att->attlen));
-		pq_sendint(&buf, atttypmod, sizeof(atttypmod));
-		/* format info appears in protocol 3.0 and up */
-		if (proto >= 3)
-		{
-			if (formats)
-				pq_sendint(&buf, formats[i], 2);
-			else
-				pq_sendint(&buf, 0, 2);
-		}
+
+		pq_sendstring(buf, NameStr(att->attname));
+		/* column ID only info appears in protocol 3.0 and up */
+		pq_sendint32(buf, atttypid);
+		pq_sendint16(buf, att->attlen);
+		pq_sendint32(buf, atttypmod);
+		/* format info only appears in protocol 3.0 and up */
 	}
-	pq_endmessage(&buf);
 }
 
 /*
@@ -252,8 +323,6 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 	int16	   *formats = myState->portal->formats;
 	int			i;
 
-	initStringInfo(&myState->buf);
-
 	/* get rid of any old data */
 	if (myState->myinfo)
 		pfree(myState->myinfo);
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index c807b00b0be..36bb8c24fb4 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -165,6 +165,10 @@ static bool RecoveryConflictPending = false;
 static bool RecoveryConflictRetryable = true;
 static ProcSignalReason RecoveryConflictReason;
 
+/* reused buffer to pass to SendRowDescriptionMessage() */
+static MemoryContext row_description_context = NULL;
+static StringInfoData row_description_buf;
+
 /* ----------------------------------------------------------------
  *		decls for routines only used in this file
  * ----------------------------------------------------------------
@@ -2371,16 +2375,17 @@ exec_describe_statement_message(const char *stmt_name)
 	/*
 	 * First describe the parameters...
 	 */
-	pq_beginmessage(&buf, 't'); /* parameter description message type */
-	pq_sendint(&buf, psrc->num_params, 2);
+	pq_beginmessage_reuse(&row_description_buf, 't'); /* parameter description
+													   * message type */
+	pq_sendint(&row_description_buf, psrc->num_params, 2);
 
 	for (i = 0; i < psrc->num_params; i++)
 	{
 		Oid			ptype = psrc->param_types[i];
 
-		pq_sendint(&buf, (int) ptype, 4);
+		pq_sendint(&row_description_buf, (int) ptype, 4);
 	}
-	pq_endmessage(&buf);
+	pq_endmessage_reuse(&row_description_buf);
 
 	/*
 	 * Next send RowDescription or NoData to describe the result...
@@ -2392,7 +2397,10 @@ exec_describe_statement_message(const char *stmt_name)
 		/* Get the plan's primary targetlist */
 		tlist = CachedPlanGetTargetList(psrc, NULL);
 
-		SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL);
+		SendRowDescriptionMessage(&row_description_buf,
+								  psrc->resultDesc,
+								  tlist,
+								  NULL);
 	}
 	else
 		pq_putemptymessage('n');	/* NoData */
@@ -2444,7 +2452,8 @@ exec_describe_portal_message(const char *portal_name)
 		return;					/* can't actually do anything... */
 
 	if (portal->tupDesc)
-		SendRowDescriptionMessage(portal->tupDesc,
+		SendRowDescriptionMessage(&row_description_buf,
+								  portal->tupDesc,
 								  FetchPortalTargetList(portal),
 								  portal->formats);
 	else
@@ -3832,6 +3841,19 @@ PostgresMain(int argc, char *argv[],
 										   "MessageContext",
 										   ALLOCSET_DEFAULT_SIZES);
 
+	/*
+	 * Create memory context and buffer used for RowDescription messages. As
+	 * SendRowDescriptionMessage(), via exec_describe_statement_message(), is
+	 * frequently executed for ever single statement, we don't want to
+	 * allocate a separate buffer every time.
+	 */
+	row_description_context = AllocSetContextCreate(TopMemoryContext,
+													"RowDescriptionContext",
+													ALLOCSET_DEFAULT_SIZES);
+	MemoryContextSwitchTo(row_description_context);
+	initStringInfo(&row_description_buf);
+	MemoryContextSwitchTo(TopMemoryContext);
+
 	/*
 	 * Remember stand-alone backend startup time
 	 */
diff --git a/src/include/access/printtup.h b/src/include/access/printtup.h
index 641715e4165..cca5b1f6002 100644
--- a/src/include/access/printtup.h
+++ b/src/include/access/printtup.h
@@ -20,8 +20,8 @@ extern DestReceiver *printtup_create_DR(CommandDest dest);
 
 extern void SetRemoteDestReceiverParams(DestReceiver *self, Portal portal);
 
-extern void SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist,
-						  int16 *formats);
+extern void SendRowDescriptionMessage(StringInfo buf,
+			 TupleDesc typeinfo, List *targetlist, int16 *formats);
 
 extern void debugStartup(DestReceiver *self, int operation,
 			 TupleDesc typeinfo);
-- 
2.14.1.536.g6867272d5b.dirty

