Speed up creation of command completion tags
authorDavid Rowley <drowley@postgresql.org>
Thu, 15 Dec 2022 21:31:25 +0000 (10:31 +1300)
committerDavid Rowley <drowley@postgresql.org>
Thu, 15 Dec 2022 21:31:25 +0000 (10:31 +1300)
The building of command completion tags could often be seen showing up in
profiles when running high tps workloads.

The query completion tags were being built with snprintf, which is slow at
the best of times when compared with more manual ways of formatting
strings.  Here we introduce BuildQueryCompletionString() to do this job
for us.  We also now store the completion tag's strlen in the
CommandTagBehavior struct so that we can quickly memcpy this number of
bytes into the completion tag string.  Appending the rows affected is done
via pg_ulltoa_n.  BuildQueryCompletionString returns the length of the
built string.  This saves us having to call strlen to figure out how many
bytes to pass to pq_putmessage().

Author: David Rowley, Andres Freund
Reviewed-by: Andres Freund
Discussion: https://postgr.es/m/CAHoyFK-Xwqc-iY52shj0G+8K9FJpse+FuZ36XBKy78wDVnd=Qg@mail.gmail.com

src/backend/tcop/cmdtag.c
src/backend/tcop/dest.c
src/include/tcop/cmdtag.h
src/include/tcop/dest.h

index 262484f561fd8c1b99c921529e5552190d7279bb..2970f680ebf98c1f388ed74c29c5936198d01a8b 100644 (file)
 
 #include "miscadmin.h"
 #include "tcop/cmdtag.h"
+#include "utils/builtins.h"
 
 
 typedef struct CommandTagBehavior
 {
-   const char *name;
+   const char *name;           /* tag name, e.g. "SELECT" */
+   const uint8 namelen;        /* set to strlen(name) */
    const bool  event_trigger_ok;
    const bool  table_rewrite_ok;
-   const bool  display_rowcount;
+   const bool  display_rowcount;   /* should the number of rows affected be
+                                    * shown in the command completion string */
 } CommandTagBehavior;
 
 #define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
-   { name, evtrgok, rwrok, rowcnt },
+   { name, (uint8) (sizeof(name) - 1), evtrgok, rwrok, rowcnt },
 
 static const CommandTagBehavior tag_behavior[COMMAND_TAG_NEXTTAG] = {
 #include "tcop/cmdtaglist.h"
@@ -47,6 +50,13 @@ GetCommandTagName(CommandTag commandTag)
    return tag_behavior[commandTag].name;
 }
 
+const char *
+GetCommandTagNameAndLen(CommandTag commandTag, Size *len)
+{
+   *len = (Size) tag_behavior[commandTag].namelen;
+   return tag_behavior[commandTag].name;
+}
+
 bool
 command_tag_display_rowcount(CommandTag commandTag)
 {
@@ -96,3 +106,59 @@ GetCommandTagEnum(const char *commandname)
    }
    return CMDTAG_UNKNOWN;
 }
+
+/*
+ * BuildQueryCompletionString
+ *     Build a string containing the command tag name with the
+ *     QueryCompletion's nprocessed for command tags with display_rowcount
+ *     set.  Returns the strlen of the constructed string.
+ *
+ * The caller must ensure that buff is at least COMPLETION_TAG_BUFSIZE bytes.
+ *
+ * If nameonly is true, then the constructed string will contain only the tag
+ * name.
+ */
+Size
+BuildQueryCompletionString(char *buff, const QueryCompletion *qc,
+                          bool nameonly)
+{
+   CommandTag  tag = qc->commandTag;
+   Size        taglen;
+   const char *tagname = GetCommandTagNameAndLen(tag, &taglen);
+   char       *bufp;
+
+   /*
+    * We assume the tagname is plain ASCII and therefore requires no encoding
+    * conversion.
+    */
+   memcpy(buff, tagname, taglen);
+   bufp = buff + taglen;
+
+   /* ensure that the tagname isn't long enough to overrun the buffer */
+   Assert(taglen <= COMPLETION_TAG_BUFSIZE - MAXINT8LEN - 4);
+
+   /*
+    * In PostgreSQL versions 11 and earlier, it was possible to create a
+    * table WITH OIDS.  When inserting into such a table, INSERT used to
+    * include the Oid of the inserted record in the completion tag.  To
+    * maintain compatibility in the wire protocol, we now write a "0" (for
+    * InvalidOid) in the location where we once wrote the new record's Oid.
+    */
+   if (command_tag_display_rowcount(tag) && !nameonly)
+   {
+       if (tag == CMDTAG_INSERT)
+       {
+           *bufp++ = ' ';
+           *bufp++ = '0';
+       }
+       *bufp++ = ' ';
+       bufp += pg_ulltoa_n(qc->nprocessed, bufp);
+   }
+
+   /* and finally, NUL terminate the string */
+   *bufp = '\0';
+
+   Assert((bufp - buff) == strlen(buff));
+
+   return bufp - buff;
+}
index 6afaa153cad6c97c010661e609111dfd8048c095..e879304ae1c58ffb05d2a9cbf8ecfbb90c52d6ef 100644 (file)
@@ -166,8 +166,7 @@ void
 EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_output)
 {
    char        completionTag[COMPLETION_TAG_BUFSIZE];
-   CommandTag  tag;
-   const char *tagname;
+   Size        len;
 
    switch (dest)
    {
@@ -175,29 +174,9 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
        case DestRemoteExecute:
        case DestRemoteSimple:
 
-           /*
-            * We assume the tagname is plain ASCII and therefore requires no
-            * encoding conversion.
-            */
-           tag = qc->commandTag;
-           tagname = GetCommandTagName(tag);
-
-           /*
-            * In PostgreSQL versions 11 and earlier, it was possible to
-            * create a table WITH OIDS.  When inserting into such a table,
-            * INSERT used to include the Oid of the inserted record in the
-            * completion tag.  To maintain compatibility in the wire
-            * protocol, we now write a "0" (for InvalidOid) in the location
-            * where we once wrote the new record's Oid.
-            */
-           if (command_tag_display_rowcount(tag) && !force_undecorated_output)
-               snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
-                        tag == CMDTAG_INSERT ?
-                        "%s 0 " UINT64_FORMAT : "%s " UINT64_FORMAT,
-                        tagname, qc->nprocessed);
-           else
-               snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s", tagname);
-           pq_putmessage('C', completionTag, strlen(completionTag) + 1);
+           len = BuildQueryCompletionString(completionTag, qc,
+                                            force_undecorated_output);
+           pq_putmessage('C', completionTag, len + 1);
 
        case DestNone:
        case DestDebug:
index 60e3179c74ee02cba91ece6d82e14fc697c29262..9bde568bb0ec5ad5a1e61bd04ba1b363eb24b632 100644 (file)
@@ -13,6 +13,8 @@
 #ifndef CMDTAG_H
 #define CMDTAG_H
 
+/* buffer size required for command completion tags */
+#define COMPLETION_TAG_BUFSIZE 64
 
 #define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
    tag,
@@ -50,9 +52,12 @@ CopyQueryCompletion(QueryCompletion *dst, const QueryCompletion *src)
 
 extern void InitializeQueryCompletion(QueryCompletion *qc);
 extern const char *GetCommandTagName(CommandTag commandTag);
+extern const char *GetCommandTagNameAndLen(CommandTag commandTag, Size *len);
 extern bool command_tag_display_rowcount(CommandTag commandTag);
 extern bool command_tag_event_trigger_ok(CommandTag commandTag);
 extern bool command_tag_table_rewrite_ok(CommandTag commandTag);
 extern CommandTag GetCommandTagEnum(const char *commandname);
+extern Size BuildQueryCompletionString(char *buff, const QueryCompletion *qc,
+                                      bool nameonly);
 
 #endif                         /* CMDTAG_H */
index 3c3eabae6749117aee3af2479a3aa770c50ac9d7..83d1af88b412bc16b53fd552b81eeb842025754a 100644 (file)
@@ -71,8 +71,6 @@
 #include "tcop/cmdtag.h"
 
 
-/* buffer size to use for command completion tags */
-#define COMPLETION_TAG_BUFSIZE 64
 
 
 /* ----------------