Improve performance of SendRowDescriptionMessage.
authorAndres Freund <andres@anarazel.de>
Wed, 11 Oct 2017 23:49:31 +0000 (16:49 -0700)
committerAndres Freund <andres@anarazel.de>
Thu, 12 Oct 2017 00:23:23 +0000 (17:23 -0700)
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
src/backend/tcop/postgres.c
src/include/access/printtup.h

index c00b372b849ad7eb355afc1e675408714922c31a..02cd1beef79b774326c26fe0e62919e414205583 100644 (file)
@@ -32,6 +32,10 @@ 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
@@ -161,7 +165,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);
 
@@ -189,61 +194,126 @@ 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(Oid)   /* resorigtbl */
+                           + sizeof(AttrNumber)    /* resorigcol */
+                           + sizeof(Oid)   /* atttypid */
+                           + sizeof(int16) /* attlen */
+                           + sizeof(int32) /* attypmod */
+                           + sizeof(int16) /* format */
+                           ) * natts);
 
    for (i = 0; i < natts; ++i)
    {
        Form_pg_attribute att = TupleDescAttr(typeinfo, i);
        Oid         atttypid = att->atttypid;
        int32       atttypmod = att->atttypmod;
+       Oid         resorigtbl;
+       AttrNumber  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);
 
-       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)
        {
-           /* 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);
-           }
+           TargetEntry *tle = (TargetEntry *) lfirst(tlist_item);
+
+           resorigtbl = tle->resorigtbl;
+           resorigcol = tle->resorigcol;
+           tlist_item = lnext(tlist_item);
        }
-       /* 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)
+       else
        {
-           if (formats)
-               pq_sendint(&buf, formats[i], 2);
-           else
-               pq_sendint(&buf, 0, 2);
+           /* 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)
+   {
+       Form_pg_attribute att = TupleDescAttr(typeinfo, i);
+       Oid         atttypid = att->atttypid;
+       int32       atttypmod = att->atttypmod;
+
+       /* If column is a domain, send the base type and typmod instead */
+       atttypid = getBaseTypeAndTypmod(atttypid, &atttypmod);
+
+       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);
 }
 
 /*
index edea6f177bb1e0d1c17c0d77e0dad2a7dc3e5b16..338ce81331d88657ef1d19a62bdccecbbd6dad23 100644 (file)
@@ -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
  * ----------------------------------------------------------------
@@ -2315,7 +2319,6 @@ static void
 exec_describe_statement_message(const char *stmt_name)
 {
    CachedPlanSource *psrc;
-   StringInfoData buf;
    int         i;
 
    /*
@@ -2371,16 +2374,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 +2396,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 +2451,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
@@ -3830,6 +3838,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
     */
index 641715e4165e911928dc8b9aeed506dd0d37d1e5..1b5a003a99216e367fa93a89e1716cf9283cf3a3 100644 (file)
@@ -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);