Hi,

On 2020-05-18 12:38:05 -0400, Tom Lane wrote:
> Andres Freund <and...@anarazel.de> writes:
> >> FWIW, I've also observed, in another thread (the node func generation
> >> thing [1]), that inlining enlargeStringInfo() helps a lot, especially
> >> when inlining some of its callers. Moving e.g. appendStringInfo() inline
> >> allows the compiler to sometimes optimize away the strlen. But if
> >> e.g. an inlined appendBinaryStringInfo() still calls enlargeStringInfo()
> >> unconditionally, successive appends cannot optimize away memory accesses
> >> for ->len/->data.
>
> > With a set of patches doing so, int4send itself is not a significant
> > factor for my test benchmark [1] anymore.
>
> This thread seems to have died out, possibly because the last set of
> patches that Andres posted was sufficiently complicated and invasive
> that nobody wanted to review it.

Well, I wasn't really planning to try to get that patchset into 13, and
it wasn't in the CF...


> I thought about this again after seeing that [1] is mostly about
> pq_begintypsend overhead

I'm not really convinced that that's the whole problem. Using the
benchmark from upthread, I get (median of three):
master: 1181.581
yours: 1171.445
mine: 598.031

That's a very significant difference, imo. It helps a bit with the
benchmark from your [1], but not that much.


> With 0001, pq_begintypsend drops from being the top single routine
> in a profile of a test case like [1] to being well down the list.
> The next biggest cost compared to text-format output is that
> printtup() itself is noticeably more expensive.  A lot of the extra
> cost there seems to be from pq_sendint32(), which is getting inlined
> into printtup(), and there probably isn't much we can do to make that
> cheaper. But eliminating a common subexpression as in 0002 below does
> help noticeably, at least with the rather old gcc I'm using.

I think there's plenty more we can do:

First, it's unnecessary to re-initialize a FunctionCallInfo for every
send/recv/out/in call. Instead we can reuse the same over and over.


After that, the biggest remaining overhead for Jack's test is the palloc
for the stringinfo, as far as I can see.  I've complained about that
before...

I've just hacked up a modification where, for send functions,
fcinfo->context contains a stringinfo set up by printtup/CopyTo. That,
combined with using a FunctionCallInfo set up beforehand, instead of
re-initializing it in every printtup cycle, results in a pretty good
saving.

Making the binary protocol 20% faster than text, in Jack's testcase. And
my lotsaints4 test, goes further down to ~410ms (this is 2.5x faster
than where started).

Now obviously, the hack with passing a StringInfo in ->context is just
that, a hack. A somewhat gross one even. But I think it pretty clearly
shows the problem and the way out.

I don't know what the best non-gross solution for the overhead of the
out/send functions is. There seems to be at least the following
major options (and a lots of variants thereof):

1) Just continue to incur significant overhead for every datum
2) Accept the uglyness of passing in a buffer via
   FunctionCallInfo->context. Change nearly all in-core send functions
   over to that.
3) Pass string buffer through an new INTERNAL argument to send/output
   function, allow both old/new style send functions. Use a wrapper
   function to adapt the "old style" to the "new style".
4) Like 3, but make the new argument optional, and use ad-hoc
   stringbuffer if not provided. I don't like the unnecessary branches
   this adds.

The biggest problem after that is that we waste a lot of time memcpying
stuff around repeatedly. There is:
1) send function: datum -> per datum stringinfo
2) printtup: per datum stringinfo -> per row stringinfo
3) socket_putmessage: per row stringinfo -> PqSendBuffer
4) send(): PqSendBuffer -> kernel buffer

It's obviously hard to avoid 1) and 4) in the common case, but the
number of other copies seem pretty clearly excessive.


If we change the signature of the out/send function to always target a
string buffer, we could pretty easily avoid 2), and for out functions
we'd not have to redundantly call strlen (as the argument to
pq_sendcountedtext) anymore, which seems substantial too.

As I argued before, I think it's unnecessary to have a separate buffer
between 3-4). We should construct the outgoing message inside the send
buffer. I still don't understand what "recursion" danger there is,
nothing below printtup should ever send protocol messages, no?


Sometimes there's also 0) in the above, when detoasting a datum...

Greetings,

Andres Freund
>From ae981295c55e7b02f81e85171ad840b6a3d5b330 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 14 Jan 2020 12:46:20 -0800
Subject: [PATCH v2 1/9] stringinfo: Move more functions inline, provide
 initStringInfoEx().

By moving the whole lifecycle of a stringinfo into inline functions,
a good compiler sometimes can be able to optimize away the existence
of the StringInfoData itself.

initStringInfoEx() allows stringinfo users to determine the initial
allocation size, avoiding over/under allocation when known.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/include/lib/stringinfo.h | 205 +++++++++++++++++++++++++++--------
 src/common/stringinfo.c      | 173 ++---------------------------
 2 files changed, 171 insertions(+), 207 deletions(-)

diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index 5a2a3dbfbc0..38211323d9d 100644
--- a/src/include/lib/stringinfo.h
+++ b/src/include/lib/stringinfo.h
@@ -18,6 +18,9 @@
 #ifndef STRINGINFO_H
 #define STRINGINFO_H
 
+#include "common/int.h"
+#include "common/string.h"
+
 /*-------------------------
  * StringInfoData holds information about an extensible string.
  *		data	is the current buffer for the string (allocated with palloc).
@@ -66,25 +69,137 @@ typedef StringInfoData *StringInfo;
  *-------------------------
  */
 
-/*------------------------
- * makeStringInfo
- * Create an empty 'StringInfoData' & return a pointer to it.
- */
-extern StringInfo makeStringInfo(void);
-
-/*------------------------
- * initStringInfo
- * Initialize a StringInfoData struct (with previously undefined contents)
- * to describe an empty string.
- */
-extern void initStringInfo(StringInfo str);
 
 /*------------------------
  * resetStringInfo
  * Clears the current content of the StringInfo, if any. The
  * StringInfo remains valid.
  */
-extern void resetStringInfo(StringInfo str);
+static inline void
+resetStringInfo(StringInfoData *pg_restrict str)
+{
+	*(char *pg_restrict) (str->data) = '\0';
+	str->len = 0;
+	str->cursor = 0;
+}
+
+/*------------------------
+ * initStringInfo
+ * Initialize a StringInfoData struct (with previously undefined contents)
+ * to describe an empty string.
+ */
+static inline void
+initStringInfo(StringInfoData *pg_restrict str)
+{
+	int			size = 1024;	/* initial default buffer size */
+
+	str->data = (char *) palloc(size);
+	str->maxlen = size;
+	resetStringInfo(str);
+}
+
+/*------------------------
+ * initStringInfoEx
+ *
+ * Like initStringInfo(), but allows to specify the size of the initial
+ * allocation.
+ */
+static inline void
+initStringInfoEx(StringInfoData *pg_restrict str, int size)
+{
+	/*
+	 * FIXME: Adding 1 to account for always present trailing \0 byte. Should
+	 * that instead be done at the caller level? Should we round up?
+	 */
+	str->data = (char *) palloc(size + 1);
+	str->maxlen = size + 1;
+	resetStringInfo(str);
+}
+
+/*------------------------
+ * makeStringInfo
+ * Create an empty 'StringInfoData' & return a pointer to it.
+ */
+static inline StringInfo
+makeStringInfo(void)
+{
+	StringInfo	res;
+
+	res = (StringInfo) palloc(sizeof(StringInfoData));
+
+	initStringInfo(res);
+
+	return res;
+}
+
+/*------------------------
+ * enlargeStringInfoImpl
+ *
+ * Actually enlarge the string, only to be called by enlargeStringInfo().
+ */
+extern void enlargeStringInfoImpl(StringInfo str, int needed);
+
+/*------------------------
+ * enlargeStringInfo
+ * Make sure a StringInfo's buffer can hold at least 'needed' more bytes.
+ *
+ * External callers usually need not concern themselves with this, since
+ * all stringinfo.c routines do it automatically.  However, if a caller
+ * knows that a StringInfo will eventually become X bytes large, it
+ * can save some palloc overhead by enlarging the buffer before starting
+ * to store data in it.
+ *
+ * NB: In the backend, because we use repalloc() to enlarge the buffer, the
+ * string buffer will remain allocated in the same memory context that was
+ * current when initStringInfo was called, even if another context is now
+ * current.  This is the desired and indeed critical behavior!
+ */
+static inline void
+enlargeStringInfo(StringInfoData *pg_restrict str, int datalen)
+{
+	int res;
+
+	if (unlikely(pg_add_s32_overflow(str->len, datalen, &res)) ||
+		unlikely(res >= str->maxlen))
+		enlargeStringInfoImpl(str, datalen);
+}
+
+/*------------------------
+ * appendBinaryStringInfoNT
+ * Append arbitrary binary data to a StringInfo, allocating more space
+ * if necessary. Does not ensure a trailing null-byte exists.
+ */
+static inline void
+appendBinaryStringInfoNT(StringInfoData *pg_restrict str, const char *pg_restrict data, int datalen)
+{
+	Assert(str != NULL);
+
+	/* Make more room if needed */
+	enlargeStringInfo(str, datalen);
+
+	/* OK, append the data */
+	memcpy((char *pg_restrict) (str->data + str->len), data, datalen);
+	str->len += datalen;
+}
+
+/*------------------------
+ * appendBinaryStringInfo
+ * Append arbitrary binary data to a StringInfo, allocating more space
+ * if necessary. Ensures that a trailing null byte is present.
+ */
+static inline void
+appendBinaryStringInfo(StringInfoData *pg_restrict str, const char *pg_restrict data, int datalen)
+{
+	appendBinaryStringInfoNT(str, data, datalen);
+
+	/*
+	 * Keep a trailing null in place, even though it's probably useless for
+	 * binary data.  (Some callers are dealing with text but call this because
+	 * their input isn't null-terminated.)
+	 */
+	*(char *pg_restrict) (str->data + str->len) = '\0';
+}
+
 
 /*------------------------
  * appendStringInfo
@@ -111,51 +226,49 @@ extern int	appendStringInfoVA(StringInfo str, const char *fmt, va_list args) pg_
  * Append a null-terminated string to str.
  * Like appendStringInfo(str, "%s", s) but faster.
  */
-extern void appendStringInfoString(StringInfo str, const char *s);
+static inline void
+appendStringInfoString(StringInfoData *pg_restrict str, const char *pg_restrict s)
+{
+	appendBinaryStringInfo(str, s, strlen(s));
+}
 
 /*------------------------
  * appendStringInfoChar
  * Append a single byte to str.
  * Like appendStringInfo(str, "%c", ch) but much faster.
  */
-extern void appendStringInfoChar(StringInfo str, char ch);
+static inline void
+appendStringInfoChar(StringInfoData *pg_restrict str, char ch)
+{
+	/* Make more room if needed */
+	enlargeStringInfo(str, 1);
 
-/*------------------------
- * appendStringInfoCharMacro
- * As above, but a macro for even more speed where it matters.
- * Caution: str argument will be evaluated multiple times.
- */
-#define appendStringInfoCharMacro(str,ch) \
-	(((str)->len + 1 >= (str)->maxlen) ? \
-	 appendStringInfoChar(str, ch) : \
-	 (void)((str)->data[(str)->len] = (ch), (str)->data[++(str)->len] = '\0'))
+	/* OK, append the character */
+	*(char *pg_restrict) (str->data + str->len) = ch;
+	str->len++;
+	*(char *pg_restrict) (str->data + str->len) = '\0';
+}
+
+/* backward compat for external code */
+#define appendStringInfoCharMacro appendStringInfoChar
 
 /*------------------------
  * appendStringInfoSpaces
  * Append a given number of spaces to str.
  */
-extern void appendStringInfoSpaces(StringInfo str, int count);
+static inline void
+appendStringInfoSpaces(StringInfoData *pg_restrict str, int count)
+{
+	if (count > 0)
+	{
+		/* Make more room if needed */
+		enlargeStringInfo(str, count);
 
-/*------------------------
- * appendBinaryStringInfo
- * Append arbitrary binary data to a StringInfo, allocating more space
- * if necessary.
- */
-extern void appendBinaryStringInfo(StringInfo str,
-								   const char *data, int datalen);
-
-/*------------------------
- * appendBinaryStringInfoNT
- * Append arbitrary binary data to a StringInfo, allocating more space
- * if necessary. Does not ensure a trailing null-byte exists.
- */
-extern void appendBinaryStringInfoNT(StringInfo str,
-									 const char *data, int datalen);
-
-/*------------------------
- * enlargeStringInfo
- * Make sure a StringInfo's buffer can hold at least 'needed' more bytes.
- */
-extern void enlargeStringInfo(StringInfo str, int needed);
+		/* OK, append the spaces */
+		memset((char *pg_restrict) (str->data + str->len), ' ', count);
+		str->len += count;
+		*(char *pg_restrict) (str->data + str->len) = '\0';
+	}
+}
 
 #endif							/* STRINGINFO_H */
diff --git a/src/common/stringinfo.c b/src/common/stringinfo.c
index 0badc465545..0ea5e0abe56 100644
--- a/src/common/stringinfo.c
+++ b/src/common/stringinfo.c
@@ -32,53 +32,6 @@
 #include "lib/stringinfo.h"
 
 
-/*
- * makeStringInfo
- *
- * Create an empty 'StringInfoData' & return a pointer to it.
- */
-StringInfo
-makeStringInfo(void)
-{
-	StringInfo	res;
-
-	res = (StringInfo) palloc(sizeof(StringInfoData));
-
-	initStringInfo(res);
-
-	return res;
-}
-
-/*
- * initStringInfo
- *
- * Initialize a StringInfoData struct (with previously undefined contents)
- * to describe an empty string.
- */
-void
-initStringInfo(StringInfo str)
-{
-	int			size = 1024;	/* initial default buffer size */
-
-	str->data = (char *) palloc(size);
-	str->maxlen = size;
-	resetStringInfo(str);
-}
-
-/*
- * resetStringInfo
- *
- * Reset the StringInfo: the data buffer remains valid, but its
- * previous content, if any, is cleared.
- */
-void
-resetStringInfo(StringInfo str)
-{
-	str->data[0] = '\0';
-	str->len = 0;
-	str->cursor = 0;
-}
-
 /*
  * appendStringInfo
  *
@@ -167,120 +120,18 @@ appendStringInfoVA(StringInfo str, const char *fmt, va_list args)
 }
 
 /*
- * appendStringInfoString
+ * enlargeStringInfoImpl
  *
- * Append a null-terminated string to str.
- * Like appendStringInfo(str, "%s", s) but faster.
+ * Make enough space for 'needed' more bytes ('needed' does not include the
+ * terminating null). This is not for external consumption, it's only to be
+ * called by enlargeStringInfo() when more space is actually needed (including
+ * when we'd overflow the maximum size).
+ *
+ * As this normally shouldn't be the common case, mark as noinline, to avoid
+ * including the function into the fastpath.
  */
-void
-appendStringInfoString(StringInfo str, const char *s)
-{
-	appendBinaryStringInfo(str, s, strlen(s));
-}
-
-/*
- * appendStringInfoChar
- *
- * Append a single byte to str.
- * Like appendStringInfo(str, "%c", ch) but much faster.
- */
-void
-appendStringInfoChar(StringInfo str, char ch)
-{
-	/* Make more room if needed */
-	if (str->len + 1 >= str->maxlen)
-		enlargeStringInfo(str, 1);
-
-	/* OK, append the character */
-	str->data[str->len] = ch;
-	str->len++;
-	str->data[str->len] = '\0';
-}
-
-/*
- * appendStringInfoSpaces
- *
- * Append the specified number of spaces to a buffer.
- */
-void
-appendStringInfoSpaces(StringInfo str, int count)
-{
-	if (count > 0)
-	{
-		/* Make more room if needed */
-		enlargeStringInfo(str, count);
-
-		/* OK, append the spaces */
-		while (--count >= 0)
-			str->data[str->len++] = ' ';
-		str->data[str->len] = '\0';
-	}
-}
-
-/*
- * appendBinaryStringInfo
- *
- * Append arbitrary binary data to a StringInfo, allocating more space
- * if necessary. Ensures that a trailing null byte is present.
- */
-void
-appendBinaryStringInfo(StringInfo str, const char *data, int datalen)
-{
-	Assert(str != NULL);
-
-	/* Make more room if needed */
-	enlargeStringInfo(str, datalen);
-
-	/* OK, append the data */
-	memcpy(str->data + str->len, data, datalen);
-	str->len += datalen;
-
-	/*
-	 * Keep a trailing null in place, even though it's probably useless for
-	 * binary data.  (Some callers are dealing with text but call this because
-	 * their input isn't null-terminated.)
-	 */
-	str->data[str->len] = '\0';
-}
-
-/*
- * appendBinaryStringInfoNT
- *
- * Append arbitrary binary data to a StringInfo, allocating more space
- * if necessary. Does not ensure a trailing null-byte exists.
- */
-void
-appendBinaryStringInfoNT(StringInfo str, const char *data, int datalen)
-{
-	Assert(str != NULL);
-
-	/* Make more room if needed */
-	enlargeStringInfo(str, datalen);
-
-	/* OK, append the data */
-	memcpy(str->data + str->len, data, datalen);
-	str->len += datalen;
-}
-
-/*
- * enlargeStringInfo
- *
- * Make sure there is enough space for 'needed' more bytes
- * ('needed' does not include the terminating null).
- *
- * External callers usually need not concern themselves with this, since
- * all stringinfo.c routines do it automatically.  However, if a caller
- * knows that a StringInfo will eventually become X bytes large, it
- * can save some palloc overhead by enlarging the buffer before starting
- * to store data in it.
- *
- * NB: In the backend, because we use repalloc() to enlarge the buffer, the
- * string buffer will remain allocated in the same memory context that was
- * current when initStringInfo was called, even if another context is now
- * current.  This is the desired and indeed critical behavior!
- */
-void
-enlargeStringInfo(StringInfo str, int needed)
+pg_noinline void
+enlargeStringInfoImpl(StringInfo str, int needed)
 {
 	int			newlen;
 
@@ -317,8 +168,8 @@ enlargeStringInfo(StringInfo str, int needed)
 
 	/* Because of the above test, we now have needed <= MaxAllocSize */
 
-	if (needed <= str->maxlen)
-		return;					/* got enough space already */
+	/* should only be called when needed */
+	Assert(needed > str->maxlen);
 
 	/*
 	 * We don't want to allocate just a little more space with each append;
-- 
2.25.0.114.g5b0ca878e0

>From f62b09d1fd2dd0d5e6b807ecb3a5481f48106692 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 14 Jan 2020 12:58:00 -0800
Subject: [PATCH v2 2/9] stringinfo: Remove in-core use of
 appendStringInfoCharMacro().

Kept seperate only to reduce size of patch moving to more inlining for
stringinfo.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/commands/copy.c          |  2 +-
 src/backend/commands/explain.c       |  8 ++++----
 src/backend/libpq/pqformat.c         |  2 +-
 src/backend/utils/adt/json.c         |  6 +++---
 src/backend/utils/adt/jsonb.c        | 10 +++++-----
 src/backend/utils/adt/jsonfuncs.c    | 28 ++++++++++++++--------------
 src/backend/utils/adt/jsonpath.c     |  6 +++---
 src/backend/utils/adt/rowtypes.c     |  8 ++++----
 src/backend/utils/adt/varlena.c      |  4 ++--
 src/backend/utils/adt/xml.c          |  2 +-
 src/backend/utils/error/elog.c       | 12 ++++++------
 src/backend/utils/mb/stringinfo_mb.c |  2 +-
 contrib/tcn/tcn.c                    | 16 ++++++++--------
 13 files changed, 53 insertions(+), 53 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 6d53dc463c1..e51c181f55c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -511,7 +511,7 @@ CopySendString(CopyState cstate, const char *str)
 static void
 CopySendChar(CopyState cstate, char c)
 {
-	appendStringInfoCharMacro(cstate->fe_msgbuf, c);
+	appendStringInfoChar(cstate->fe_msgbuf, c);
 }
 
 static void
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index efd7201d611..08a0b069ed1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -4551,16 +4551,16 @@ ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
 
 	if ((flags & X_NOWHITESPACE) == 0)
 		appendStringInfoSpaces(es->str, 2 * es->indent);
-	appendStringInfoCharMacro(es->str, '<');
+	appendStringInfoChar(es->str, '<');
 	if ((flags & X_CLOSING) != 0)
-		appendStringInfoCharMacro(es->str, '/');
+		appendStringInfoChar(es->str, '/');
 	for (s = tagname; *s; s++)
 		appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
 	if ((flags & X_CLOSE_IMMEDIATE) != 0)
 		appendStringInfoString(es->str, " /");
-	appendStringInfoCharMacro(es->str, '>');
+	appendStringInfoChar(es->str, '>');
 	if ((flags & X_NOWHITESPACE) == 0)
-		appendStringInfoCharMacro(es->str, '\n');
+		appendStringInfoChar(es->str, '\n');
 }
 
 /*
diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c
index a6f990c2d29..999252681c2 100644
--- a/src/backend/libpq/pqformat.c
+++ b/src/backend/libpq/pqformat.c
@@ -234,7 +234,7 @@ pq_send_ascii_string(StringInfo buf, const char *str)
 
 		if (IS_HIGHBIT_SET(ch))
 			ch = '?';
-		appendStringInfoCharMacro(buf, ch);
+		appendStringInfoChar(buf, ch);
 	}
 	appendStringInfoChar(buf, '\0');
 }
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 641ae3fdf8e..08f3eeb927f 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1280,7 +1280,7 @@ escape_json(StringInfo buf, const char *str)
 {
 	const char *p;
 
-	appendStringInfoCharMacro(buf, '"');
+	appendStringInfoChar(buf, '"');
 	for (p = str; *p; p++)
 	{
 		switch (*p)
@@ -1310,11 +1310,11 @@ escape_json(StringInfo buf, const char *str)
 				if ((unsigned char) *p < ' ')
 					appendStringInfo(buf, "\\u%04x", (int) *p);
 				else
-					appendStringInfoCharMacro(buf, *p);
+					appendStringInfoChar(buf, *p);
 				break;
 		}
 	}
-	appendStringInfoCharMacro(buf, '"');
+	appendStringInfoChar(buf, '"');
 }
 
 /*
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 1e9ca046c69..7e8792dff62 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -515,7 +515,7 @@ JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool
 				if (!v.val.array.rawScalar)
 				{
 					add_indent(out, use_indent && !last_was_key, level);
-					appendStringInfoCharMacro(out, '[');
+					appendStringInfoChar(out, '[');
 				}
 				else
 					raw_scalar = true;
@@ -528,7 +528,7 @@ JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool
 					appendBinaryStringInfo(out, ", ", ispaces);
 
 				add_indent(out, use_indent && !last_was_key, level);
-				appendStringInfoCharMacro(out, '{');
+				appendStringInfoChar(out, '{');
 
 				first = true;
 				level++;
@@ -576,14 +576,14 @@ JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool
 				if (!raw_scalar)
 				{
 					add_indent(out, use_indent, level);
-					appendStringInfoCharMacro(out, ']');
+					appendStringInfoChar(out, ']');
 				}
 				first = false;
 				break;
 			case WJB_END_OBJECT:
 				level--;
 				add_indent(out, use_indent, level);
-				appendStringInfoCharMacro(out, '}');
+				appendStringInfoChar(out, '}');
 				first = false;
 				break;
 			default:
@@ -605,7 +605,7 @@ add_indent(StringInfo out, bool indent, int level)
 	{
 		int			i;
 
-		appendStringInfoCharMacro(out, '\n');
+		appendStringInfoChar(out, '\n');
 		for (i = 0; i < level; i++)
 			appendBinaryStringInfo(out, "    ", 4);
 	}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 5a09d65fdce..eefb402e010 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -3979,7 +3979,7 @@ sn_object_start(void *state)
 {
 	StripnullState *_state = (StripnullState *) state;
 
-	appendStringInfoCharMacro(_state->strval, '{');
+	appendStringInfoChar(_state->strval, '{');
 }
 
 static void
@@ -3987,7 +3987,7 @@ sn_object_end(void *state)
 {
 	StripnullState *_state = (StripnullState *) state;
 
-	appendStringInfoCharMacro(_state->strval, '}');
+	appendStringInfoChar(_state->strval, '}');
 }
 
 static void
@@ -3995,7 +3995,7 @@ sn_array_start(void *state)
 {
 	StripnullState *_state = (StripnullState *) state;
 
-	appendStringInfoCharMacro(_state->strval, '[');
+	appendStringInfoChar(_state->strval, '[');
 }
 
 static void
@@ -4003,7 +4003,7 @@ sn_array_end(void *state)
 {
 	StripnullState *_state = (StripnullState *) state;
 
-	appendStringInfoCharMacro(_state->strval, ']');
+	appendStringInfoChar(_state->strval, ']');
 }
 
 static void
@@ -4023,7 +4023,7 @@ sn_object_field_start(void *state, char *fname, bool isnull)
 	}
 
 	if (_state->strval->data[_state->strval->len - 1] != '{')
-		appendStringInfoCharMacro(_state->strval, ',');
+		appendStringInfoChar(_state->strval, ',');
 
 	/*
 	 * Unfortunately we don't have the quoted and escaped string any more, so
@@ -4031,7 +4031,7 @@ sn_object_field_start(void *state, char *fname, bool isnull)
 	 */
 	escape_json(_state->strval, fname);
 
-	appendStringInfoCharMacro(_state->strval, ':');
+	appendStringInfoChar(_state->strval, ':');
 }
 
 static void
@@ -4040,7 +4040,7 @@ sn_array_element_start(void *state, bool isnull)
 	StripnullState *_state = (StripnullState *) state;
 
 	if (_state->strval->data[_state->strval->len - 1] != '[')
-		appendStringInfoCharMacro(_state->strval, ',');
+		appendStringInfoChar(_state->strval, ',');
 }
 
 static void
@@ -5364,7 +5364,7 @@ transform_string_values_object_start(void *state)
 {
 	TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
 
-	appendStringInfoCharMacro(_state->strval, '{');
+	appendStringInfoChar(_state->strval, '{');
 }
 
 static void
@@ -5372,7 +5372,7 @@ transform_string_values_object_end(void *state)
 {
 	TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
 
-	appendStringInfoCharMacro(_state->strval, '}');
+	appendStringInfoChar(_state->strval, '}');
 }
 
 static void
@@ -5380,7 +5380,7 @@ transform_string_values_array_start(void *state)
 {
 	TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
 
-	appendStringInfoCharMacro(_state->strval, '[');
+	appendStringInfoChar(_state->strval, '[');
 }
 
 static void
@@ -5388,7 +5388,7 @@ transform_string_values_array_end(void *state)
 {
 	TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
 
-	appendStringInfoCharMacro(_state->strval, ']');
+	appendStringInfoChar(_state->strval, ']');
 }
 
 static void
@@ -5397,14 +5397,14 @@ transform_string_values_object_field_start(void *state, char *fname, bool isnull
 	TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
 
 	if (_state->strval->data[_state->strval->len - 1] != '{')
-		appendStringInfoCharMacro(_state->strval, ',');
+		appendStringInfoChar(_state->strval, ',');
 
 	/*
 	 * Unfortunately we don't have the quoted and escaped string any more, so
 	 * we have to re-escape it.
 	 */
 	escape_json(_state->strval, fname);
-	appendStringInfoCharMacro(_state->strval, ':');
+	appendStringInfoChar(_state->strval, ':');
 }
 
 static void
@@ -5413,7 +5413,7 @@ transform_string_values_array_element_start(void *state, bool isnull)
 	TransformJsonStringValuesState *_state = (TransformJsonStringValuesState *) state;
 
 	if (_state->strval->data[_state->strval->len - 1] != '[')
-		appendStringInfoCharMacro(_state->strval, ',');
+		appendStringInfoChar(_state->strval, ',');
 }
 
 static void
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 3c0dc38a7f8..43d5914477a 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -441,13 +441,13 @@ alignStringInfoInt(StringInfo buf)
 	switch (INTALIGN(buf->len) - buf->len)
 	{
 		case 3:
-			appendStringInfoCharMacro(buf, 0);
+			appendStringInfoChar(buf, 0);
 			/* FALLTHROUGH */
 		case 2:
-			appendStringInfoCharMacro(buf, 0);
+			appendStringInfoChar(buf, 0);
 			/* FALLTHROUGH */
 		case 1:
-			appendStringInfoCharMacro(buf, 0);
+			appendStringInfoChar(buf, 0);
 			/* FALLTHROUGH */
 		default:
 			break;
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index 80cba2f4c26..987ebc9d7f8 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -424,17 +424,17 @@ record_out(PG_FUNCTION_ARGS)
 
 		/* And emit the string */
 		if (nq)
-			appendStringInfoCharMacro(&buf, '"');
+			appendStringInfoChar(&buf, '"');
 		for (tmp = value; *tmp; tmp++)
 		{
 			char		ch = *tmp;
 
 			if (ch == '"' || ch == '\\')
-				appendStringInfoCharMacro(&buf, ch);
-			appendStringInfoCharMacro(&buf, ch);
+				appendStringInfoChar(&buf, ch);
+			appendStringInfoChar(&buf, ch);
 		}
 		if (nq)
-			appendStringInfoCharMacro(&buf, '"');
+			appendStringInfoChar(&buf, '"');
 	}
 
 	appendStringInfoChar(&buf, ')');
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 2eaabd6231d..f3c1a63455a 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -5557,7 +5557,7 @@ text_format(PG_FUNCTION_ARGS)
 		 */
 		if (*cp != '%')
 		{
-			appendStringInfoCharMacro(&str, *cp);
+			appendStringInfoChar(&str, *cp);
 			continue;
 		}
 
@@ -5566,7 +5566,7 @@ text_format(PG_FUNCTION_ARGS)
 		/* Easy case: %% outputs a single % */
 		if (*cp == '%')
 		{
-			appendStringInfoCharMacro(&str, *cp);
+			appendStringInfoChar(&str, *cp);
 			continue;
 		}
 
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 4c299057a6f..9718cd05912 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -2373,7 +2373,7 @@ escape_xml(const char *str)
 				appendStringInfoString(&buf, "&#x0d;");
 				break;
 			default:
-				appendStringInfoCharMacro(&buf, *p);
+				appendStringInfoChar(&buf, *p);
 				break;
 		}
 	}
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e9762010309..3697bdc53aa 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -2630,14 +2630,14 @@ appendCSVLiteral(StringInfo buf, const char *data)
 	if (p == NULL)
 		return;
 
-	appendStringInfoCharMacro(buf, '"');
+	appendStringInfoChar(buf, '"');
 	while ((c = *p++) != '\0')
 	{
 		if (c == '"')
-			appendStringInfoCharMacro(buf, '"');
-		appendStringInfoCharMacro(buf, c);
+			appendStringInfoChar(buf, '"');
+		appendStringInfoChar(buf, c);
 	}
-	appendStringInfoCharMacro(buf, '"');
+	appendStringInfoChar(buf, '"');
 }
 
 /*
@@ -3407,9 +3407,9 @@ append_with_tabs(StringInfo buf, const char *str)
 
 	while ((ch = *str++) != '\0')
 	{
-		appendStringInfoCharMacro(buf, ch);
+		appendStringInfoChar(buf, ch);
 		if (ch == '\n')
-			appendStringInfoCharMacro(buf, '\t');
+			appendStringInfoChar(buf, '\t');
 	}
 }
 
diff --git a/src/backend/utils/mb/stringinfo_mb.c b/src/backend/utils/mb/stringinfo_mb.c
index 5f51f538c18..a9f43d0b32b 100644
--- a/src/backend/utils/mb/stringinfo_mb.c
+++ b/src/backend/utils/mb/stringinfo_mb.c
@@ -61,7 +61,7 @@ appendStringInfoStringQuoted(StringInfo str, const char *s, int maxlen)
 		ellipsis = false;
 	}
 
-	appendStringInfoCharMacro(str, '\'');
+	appendStringInfoChar(str, '\'');
 
 	while ((chunk_end = strchr(chunk_search_start, '\'')) != NULL)
 	{
diff --git a/contrib/tcn/tcn.c b/contrib/tcn/tcn.c
index 552f107bf6b..d38d6e3bca8 100644
--- a/contrib/tcn/tcn.c
+++ b/contrib/tcn/tcn.c
@@ -32,15 +32,15 @@ PG_MODULE_MAGIC;
 static void
 strcpy_quoted(StringInfo r, const char *s, const char q)
 {
-	appendStringInfoCharMacro(r, q);
+	appendStringInfoChar(r, q);
 	while (*s)
 	{
 		if (*s == q)
-			appendStringInfoCharMacro(r, q);
-		appendStringInfoCharMacro(r, *s);
+			appendStringInfoChar(r, q);
+		appendStringInfoChar(r, *s);
 		s++;
 	}
-	appendStringInfoCharMacro(r, q);
+	appendStringInfoChar(r, q);
 }
 
 /*
@@ -147,17 +147,17 @@ triggered_change_notification(PG_FUNCTION_ARGS)
 				foundPK = true;
 
 				strcpy_quoted(payload, RelationGetRelationName(rel), '"');
-				appendStringInfoCharMacro(payload, ',');
-				appendStringInfoCharMacro(payload, operation);
+				appendStringInfoChar(payload, ',');
+				appendStringInfoChar(payload, operation);
 
 				for (i = 0; i < indnkeyatts; i++)
 				{
 					int			colno = index->indkey.values[i];
 					Form_pg_attribute attr = TupleDescAttr(tupdesc, colno - 1);
 
-					appendStringInfoCharMacro(payload, ',');
+					appendStringInfoChar(payload, ',');
 					strcpy_quoted(payload, NameStr(attr->attname), '"');
-					appendStringInfoCharMacro(payload, '=');
+					appendStringInfoChar(payload, '=');
 					strcpy_quoted(payload, SPI_getvalue(trigtuple, tupdesc, colno), '\'');
 				}
 
-- 
2.25.0.114.g5b0ca878e0

>From f351d204bb6151d5deca2b0cbf3e92ce277323c8 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 14 Jan 2020 12:50:06 -0800
Subject: [PATCH v2 3/9] pqformat: Move functions for type sending inline, add
 pq_begintypsend_ex().

Combined with the previous commit inlining more functions in
stringinfo this allows many type send functions to be optimized
considerably better, optimizing away the StringInfoData entirely.

The new pq_begintypsend_ex() is useful to avoid over-allocating for
send functions that only output a small amount of data, e.g. int4send
now allocates 9 bytes, instead of 1024.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/include/libpq/pqformat.h | 52 ++++++++++++++++++++++++++++++++++++
 src/backend/libpq/pqformat.c | 38 --------------------------
 2 files changed, 52 insertions(+), 38 deletions(-)

diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index af31e9caba3..e0dbb783c6a 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -31,6 +31,58 @@ extern void pq_send_ascii_string(StringInfo buf, const char *str);
 extern void pq_sendfloat4(StringInfo buf, float4 f);
 extern void pq_sendfloat8(StringInfo buf, float8 f);
 
+
+/* --------------------------------
+ *		pq_begintypsend		- initialize for constructing a bytea result
+ * --------------------------------
+ */
+static inline void
+pq_begintypsend(StringInfo buf)
+{
+	initStringInfo(buf);
+	/* Reserve four bytes for the bytea length word */
+	appendStringInfoSpaces(buf, 4);
+}
+
+/* --------------------------------
+ *		pq_begintypsend_ex	- like pq_begintypesend, but with length hint
+ *
+ * This can be used over pq_begintypesend if the caller can cheaply determine
+ * how much data will be sent, reducing the initial size of the
+ * StringInfo. The passed in size need not include the overhead of the length
+ * word.
+ * --------------------------------
+ */
+static inline void
+pq_begintypsend_ex(StringInfo buf, int size)
+{
+	initStringInfoEx(buf, size + 4);
+	/* Reserve four bytes for the bytea length word */
+	appendStringInfoSpaces(buf, 4);
+}
+
+
+/* --------------------------------
+ *		pq_endtypsend	- finish constructing a bytea result
+ *
+ * The data buffer is returned as the palloc'd bytea value.  (We expect
+ * that it will be suitably aligned for this because it has been palloc'd.)
+ * We assume the StringInfoData is just a local variable in the caller and
+ * need not be pfree'd.
+ * --------------------------------
+ */
+static inline bytea *
+pq_endtypsend(StringInfo buf)
+{
+	bytea	   *result = (bytea *) buf->data;
+
+	/* Insert correct length into bytea length word */
+	Assert(buf->len >= VARHDRSZ);
+	SET_VARSIZE(result, buf->len);
+
+	return result;
+}
+
 /*
  * Append a [u]int8 to a StringInfo buffer, which already has enough space
  * preallocated.
diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c
index 999252681c2..347a0aa697a 100644
--- a/src/backend/libpq/pqformat.c
+++ b/src/backend/libpq/pqformat.c
@@ -319,44 +319,6 @@ pq_endmessage_reuse(StringInfo buf)
 	(void) pq_putmessage(buf->cursor, buf->data, buf->len);
 }
 
-
-/* --------------------------------
- *		pq_begintypsend		- initialize for constructing a bytea result
- * --------------------------------
- */
-void
-pq_begintypsend(StringInfo buf)
-{
-	initStringInfo(buf);
-	/* Reserve four bytes for the bytea length word */
-	appendStringInfoCharMacro(buf, '\0');
-	appendStringInfoCharMacro(buf, '\0');
-	appendStringInfoCharMacro(buf, '\0');
-	appendStringInfoCharMacro(buf, '\0');
-}
-
-/* --------------------------------
- *		pq_endtypsend	- finish constructing a bytea result
- *
- * The data buffer is returned as the palloc'd bytea value.  (We expect
- * that it will be suitably aligned for this because it has been palloc'd.)
- * We assume the StringInfoData is just a local variable in the caller and
- * need not be pfree'd.
- * --------------------------------
- */
-bytea *
-pq_endtypsend(StringInfo buf)
-{
-	bytea	   *result = (bytea *) buf->data;
-
-	/* Insert correct length into bytea length word */
-	Assert(buf->len >= VARHDRSZ);
-	SET_VARSIZE(result, buf->len);
-
-	return result;
-}
-
-
 /* --------------------------------
  *		pq_puttextmessage - generate a character set-converted message in one step
  *
-- 
2.25.0.114.g5b0ca878e0

>From 03037c6590589d6a10d45aa6b9bd8aa0add2b817 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 14 Jan 2020 12:53:33 -0800
Subject: [PATCH v2 4/9] WIP: Annotate palloc() with malloc and other compiler
 attributes.

In particularly malloc is useful, allowing the compiler to realize
that the return value does not alias with other data, allowing other
optimizations.

If we were to do this, we'd obviously need to hide these behind
macros to support compilers without those attributes.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/include/utils/palloc.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/include/utils/palloc.h b/src/include/utils/palloc.h
index cc356a63728..a611148be67 100644
--- a/src/include/utils/palloc.h
+++ b/src/include/utils/palloc.h
@@ -74,7 +74,7 @@ extern void *MemoryContextAllocZeroAligned(MemoryContext context, Size size);
 extern void *MemoryContextAllocExtended(MemoryContext context,
 										Size size, int flags);
 
-extern void *palloc(Size size);
+extern void *palloc(Size size) __attribute__((malloc, alloc_size(1), assume_aligned(MAXIMUM_ALIGNOF), returns_nonnull, warn_unused_result));
 extern void *palloc0(Size size);
 extern void *palloc_extended(Size size, int flags);
 extern void *repalloc(void *pointer, Size size);
-- 
2.25.0.114.g5b0ca878e0

>From 57f228a71d886bf1ae4cf7e83fc8906dba021317 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 14 Jan 2020 12:58:55 -0800
Subject: [PATCH v2 5/9] Move {int4,int8}send to use pq_begintypsend_ex.

If we were to introduce pq_begintypsend_ex(), we'd probably want to do
this to quite a few more functions.
---
 src/backend/utils/adt/int.c  | 2 +-
 src/backend/utils/adt/int8.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 63c59c56b3f..84d22fc5b99 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -305,7 +305,7 @@ int4send(PG_FUNCTION_ARGS)
 	int32		arg1 = PG_GETARG_INT32(0);
 	StringInfoData buf;
 
-	pq_begintypsend(&buf);
+	pq_begintypsend_ex(&buf, 4);
 	pq_sendint32(&buf, arg1);
 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index abba8f1df04..b258778622d 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -175,7 +175,7 @@ int8send(PG_FUNCTION_ARGS)
 	int64		arg1 = PG_GETARG_INT64(0);
 	StringInfoData buf;
 
-	pq_begintypsend(&buf);
+	pq_begintypsend_ex(&buf, 8);
 	pq_sendint64(&buf, arg1);
 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 }
-- 
2.25.0.114.g5b0ca878e0

>From 18642eec1e616b914fc9c8a98e1bb258ceb8975a Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 14 Jan 2020 13:00:26 -0800
Subject: [PATCH v2 6/9] copy: Use appendBinaryStringInfoNT() for sending
 binary data.

Maintaining the trailing byte is useless overhead.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/commands/copy.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e51c181f55c..c12855a304a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -499,13 +499,13 @@ SendCopyEnd(CopyState cstate)
 static void
 CopySendData(CopyState cstate, const void *databuf, int datasize)
 {
-	appendBinaryStringInfo(cstate->fe_msgbuf, databuf, datasize);
+	appendBinaryStringInfoNT(cstate->fe_msgbuf, databuf, datasize);
 }
 
 static void
 CopySendString(CopyState cstate, const char *str)
 {
-	appendBinaryStringInfo(cstate->fe_msgbuf, str, strlen(str));
+	appendBinaryStringInfoNT(cstate->fe_msgbuf, str, strlen(str));
 }
 
 static void
-- 
2.25.0.114.g5b0ca878e0

>From ff2892ecd7ceebde13321bade5b9855433da3295 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 2 Jun 2020 16:47:26 -0700
Subject: [PATCH v2 7/9] wip: make send/recv calls in printtup.c & copy.c
 cheaper.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/access/common/printtup.c |  37 ++++-
 src/backend/commands/copy.c          | 208 ++++++++++++++++++++-------
 2 files changed, 189 insertions(+), 56 deletions(-)

diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index dd1bac0aa9e..db23bb99c16 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -56,6 +56,15 @@ typedef struct
 	bool		typisvarlena;	/* is it varlena (ie possibly toastable)? */
 	int16		format;			/* format code for this column */
 	FmgrInfo	finfo;			/* Precomputed call info for output fn */
+
+	/* use union with FunctionCallInfoBaseData to guarantee alignment */
+	union
+	{
+		FunctionCallInfoBaseData fcinfo;
+		/* ensure enough space for nargs args is available */
+		char fcinfo_data[SizeForFunctionCallInfo(1)];
+	} fcinfo_data;
+
 } PrinttupAttrInfo;
 
 typedef struct
@@ -355,6 +364,9 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 							  &thisState->typoutput,
 							  &thisState->typisvarlena);
 			fmgr_info(thisState->typoutput, &thisState->finfo);
+			InitFunctionCallInfoData(thisState->fcinfo_data.fcinfo,
+									 &thisState->finfo, 1, InvalidOid,
+									 NULL, NULL);
 		}
 		else if (format == 1)
 		{
@@ -362,6 +374,9 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 									&thisState->typsend,
 									&thisState->typisvarlena);
 			fmgr_info(thisState->typsend, &thisState->finfo);
+			InitFunctionCallInfoData(thisState->fcinfo_data.fcinfo,
+									 &thisState->finfo, 1, InvalidOid,
+									 NULL, NULL);
 		}
 		else
 			ereport(ERROR,
@@ -438,11 +453,25 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 		{
 			/* Binary output */
 			bytea	   *outputbytes;
+			int			outputlen;
+			Datum		result;
+			FunctionCallInfo fcinfo = &thisState->fcinfo_data.fcinfo;
 
-			outputbytes = SendFunctionCall(&thisState->finfo, attr);
-			pq_sendint32(buf, VARSIZE(outputbytes) - VARHDRSZ);
-			pq_sendbytes(buf, VARDATA(outputbytes),
-						 VARSIZE(outputbytes) - VARHDRSZ);
+			fcinfo->args[0].value = attr;
+			fcinfo->args[0].isnull = false;
+			result = FunctionCallInvoke(fcinfo);
+
+			/* Check for null result, since caller is clearly not expecting one */
+			if (unlikely(fcinfo->isnull))
+				elog(ERROR, "send function return null");
+
+			outputbytes = DatumGetByteaP(result);
+			outputlen = VARSIZE(outputbytes) - VARHDRSZ;
+
+			Assert(outputlen > 0);
+
+			pq_sendint32(buf, outputlen);
+			pq_sendbytes(buf, VARDATA(outputbytes), outputlen);
 		}
 	}
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index c12855a304a..bec1ce51260 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -95,6 +95,37 @@ typedef enum CopyInsertMethod
 	CIM_MULTI_CONDITIONAL		/* use table_multi_insert only if valid */
 } CopyInsertMethod;
 
+typedef struct CopyInAttributeInfo
+{
+	AttrNumber num;
+	const char *name;
+
+	Oid		   typioparam;
+	int32	   typmod;
+
+	FmgrInfo in_finfo;
+	union
+	{
+		FunctionCallInfoBaseData fcinfo;
+		char fcinfo_data[SizeForFunctionCallInfo(3)];
+	} in_fcinfo;
+
+} CopyInAttributeInfo;
+
+typedef struct CopyOutAttributeInfo
+{
+	AttrNumber num;
+	const char *name;
+	int32	   typid;
+
+	FmgrInfo out_finfo;
+	union
+	{
+		FunctionCallInfoBaseData fcinfo;
+		char fcinfo_data[SizeForFunctionCallInfo(1)];
+	} out_fcinfo;
+} CopyOutAttributeInfo;
+
 /*
  * This struct contains all the state variables used throughout a COPY
  * operation. For simplicity, we use the same struct for all variants of COPY,
@@ -169,15 +200,14 @@ typedef struct CopyStateData
 	/*
 	 * Working state for COPY TO
 	 */
-	FmgrInfo   *out_functions;	/* lookup info for output functions */
+	CopyOutAttributeInfo *out_attributes;
 	MemoryContext rowcontext;	/* per-row evaluation context */
 
 	/*
 	 * Working state for COPY FROM
 	 */
 	AttrNumber	num_defaults;
-	FmgrInfo   *in_functions;	/* array of input functions for each attrs */
-	Oid		   *typioparams;	/* array of element types for in_functions */
+	CopyInAttributeInfo *in_attributes;
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
 	bool		volatile_defexprs;	/* is any of defexprs volatile? */
@@ -368,8 +398,8 @@ static bool CopyReadLineText(CopyState cstate);
 static int	CopyReadAttributesText(CopyState cstate);
 static int	CopyReadAttributesCSV(CopyState cstate);
 static Datum CopyReadBinaryAttribute(CopyState cstate,
-									 int column_no, FmgrInfo *flinfo,
-									 Oid typioparam, int32 typmod,
+									 int column_no,
+									 CopyInAttributeInfo *attr,
 									 bool *isnull);
 static void CopyAttributeOutText(CopyState cstate, char *string);
 static void CopyAttributeOutCSV(CopyState cstate, char *string,
@@ -2027,23 +2057,34 @@ CopyTo(CopyState cstate)
 	cstate->fe_msgbuf = makeStringInfo();
 
 	/* Get info about the columns we need to process. */
-	cstate->out_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
+	cstate->out_attributes =
+		(CopyOutAttributeInfo *) palloc(num_phys_attrs * sizeof(CopyOutAttributeInfo));
 	foreach(cur, cstate->attnumlist)
 	{
 		int			attnum = lfirst_int(cur);
+		CopyOutAttributeInfo *attr = &cstate->out_attributes[attnum - 1];
+		Form_pg_attribute pgatt;
 		Oid			out_func_oid;
 		bool		isvarlena;
-		Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
+
+		pgatt = TupleDescAttr(tupDesc, attnum - 1);
+		attr->num = attnum;
+		attr->name = NameStr(pgatt->attname);
+		attr->typid = pgatt->atttypid;
 
 		if (cstate->binary)
-			getTypeBinaryOutputInfo(attr->atttypid,
+			getTypeBinaryOutputInfo(attr->typid,
 									&out_func_oid,
 									&isvarlena);
 		else
-			getTypeOutputInfo(attr->atttypid,
+			getTypeOutputInfo(attr->typid,
 							  &out_func_oid,
 							  &isvarlena);
-		fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
+
+		fmgr_info(out_func_oid, &attr->out_finfo);
+		InitFunctionCallInfoData(attr->out_fcinfo.fcinfo, &attr->out_finfo,
+								 1, InvalidOid,
+								 NULL, NULL);
 	}
 
 	/*
@@ -2156,7 +2197,7 @@ static void
 CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 {
 	bool		need_delim = false;
-	FmgrInfo   *out_functions = cstate->out_functions;
+	CopyOutAttributeInfo *out_attributes = cstate->out_attributes;
 	MemoryContext oldcontext;
 	ListCell   *cur;
 	char	   *string;
@@ -2176,6 +2217,8 @@ CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 	foreach(cur, cstate->attnumlist)
 	{
 		int			attnum = lfirst_int(cur);
+		CopyOutAttributeInfo *attr = &out_attributes[attnum - 1];
+		FunctionCallInfo fcinfo = &attr->out_fcinfo.fcinfo;
 		Datum		value = slot->tts_values[attnum - 1];
 		bool		isnull = slot->tts_isnull[attnum - 1];
 
@@ -2197,8 +2240,19 @@ CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 		{
 			if (!cstate->binary)
 			{
-				string = OutputFunctionCall(&out_functions[attnum - 1],
-											value);
+				Datum		result;
+
+				fcinfo->args[0].value = value;
+				fcinfo->args[0].isnull = false;
+
+				result = FunctionCallInvoke(fcinfo);
+
+				/* Check for null result, since caller is clearly not expecting one */
+				if (unlikely(fcinfo->isnull))
+					elog(ERROR, "send function return null");
+
+				string = DatumGetCString(result);
+
 				if (cstate->csv_mode)
 					CopyAttributeOutCSV(cstate, string,
 										cstate->force_quote_flags[attnum - 1],
@@ -2208,13 +2262,23 @@ CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 			}
 			else
 			{
+				int			outputlen;
+				Datum		result;
 				bytea	   *outputbytes;
 
-				outputbytes = SendFunctionCall(&out_functions[attnum - 1],
-											   value);
-				CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ);
-				CopySendData(cstate, VARDATA(outputbytes),
-							 VARSIZE(outputbytes) - VARHDRSZ);
+				fcinfo->args[0].value = value;
+				fcinfo->args[0].isnull = false;
+				result = FunctionCallInvoke(fcinfo);
+
+				/* Check for null result, since caller is clearly not expecting one */
+				if (unlikely(fcinfo->isnull))
+					elog(ERROR, "send function return null");
+
+				outputbytes = DatumGetByteaP(result);
+				outputlen = VARSIZE(outputbytes) - VARHDRSZ;
+
+				CopySendInt32(cstate, outputlen);
+				CopySendData(cstate, VARDATA(outputbytes), outputlen);
 			}
 		}
 	}
@@ -3344,8 +3408,7 @@ BeginCopyFrom(ParseState *pstate,
 	TupleDesc	tupDesc;
 	AttrNumber	num_phys_attrs,
 				num_defaults;
-	FmgrInfo   *in_functions;
-	Oid		   *typioparams;
+	CopyInAttributeInfo *in_attributes;
 	int			attnum;
 	Oid			in_func_oid;
 	int		   *defmap;
@@ -3386,30 +3449,39 @@ BeginCopyFrom(ParseState *pstate,
 	 * the input function), and info about defaults and constraints. (Which
 	 * input function we use depends on text/binary format choice.)
 	 */
-	in_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
-	typioparams = (Oid *) palloc(num_phys_attrs * sizeof(Oid));
+	in_attributes = (CopyInAttributeInfo * ) palloc0(num_phys_attrs * sizeof(CopyInAttributeInfo));
+
 	defmap = (int *) palloc(num_phys_attrs * sizeof(int));
 	defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *));
 
 	for (attnum = 1; attnum <= num_phys_attrs; attnum++)
 	{
-		Form_pg_attribute att = TupleDescAttr(tupDesc, attnum - 1);
+		CopyInAttributeInfo *attr = &in_attributes[attnum - 1];
+		Form_pg_attribute pgatt = TupleDescAttr(tupDesc, attnum - 1);
 
 		/* We don't need info for dropped attributes */
-		if (att->attisdropped)
+		if (pgatt->attisdropped)
 			continue;
 
+		attr->num = attnum;
+		attr->typmod = pgatt->atttypmod;
+		attr->name = NameStr(pgatt->attname);
+
 		/* Fetch the input function and typioparam info */
 		if (cstate->binary)
-			getTypeBinaryInputInfo(att->atttypid,
-								   &in_func_oid, &typioparams[attnum - 1]);
+			getTypeBinaryInputInfo(pgatt->atttypid,
+								   &in_func_oid, &attr->typioparam);
 		else
-			getTypeInputInfo(att->atttypid,
-							 &in_func_oid, &typioparams[attnum - 1]);
-		fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+			getTypeInputInfo(pgatt->atttypid,
+							 &in_func_oid, &attr->typioparam);
+		attr->typmod = pgatt->atttypmod;
+
+		fmgr_info(in_func_oid, &attr->in_finfo);
+		InitFunctionCallInfoData(attr->in_fcinfo.fcinfo, &attr->in_finfo, 3,
+								 InvalidOid, NULL, NULL);
 
 		/* Get default info if needed */
-		if (!list_member_int(cstate->attnumlist, attnum) && !att->attgenerated)
+		if (!list_member_int(cstate->attnumlist, attnum) && !pgatt->attgenerated)
 		{
 			/* attribute is NOT to be copied from input */
 			/* use default value if one exists */
@@ -3446,8 +3518,7 @@ BeginCopyFrom(ParseState *pstate,
 	}
 
 	/* We keep those variables in cstate. */
-	cstate->in_functions = in_functions;
-	cstate->typioparams = typioparams;
+	cstate->in_attributes = in_attributes;
 	cstate->defmap = defmap;
 	cstate->defexprs = defexprs;
 	cstate->volatile_defexprs = volatile_defexprs;
@@ -3639,8 +3710,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 	AttrNumber	num_phys_attrs,
 				attr_count,
 				num_defaults = cstate->num_defaults;
-	FmgrInfo   *in_functions = cstate->in_functions;
-	Oid		   *typioparams = cstate->typioparams;
+	CopyInAttributeInfo *in_attributes = cstate->in_attributes;
 	int			i;
 	int		   *defmap = cstate->defmap;
 	ExprState **defexprs = cstate->defexprs;
@@ -3678,13 +3748,14 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 		{
 			int			attnum = lfirst_int(cur);
 			int			m = attnum - 1;
-			Form_pg_attribute att = TupleDescAttr(tupDesc, m);
+			CopyInAttributeInfo *attr = &in_attributes[m];
+			FunctionCallInfo fcinfo = &attr->in_fcinfo.fcinfo;
 
 			if (fieldno >= fldct)
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 						 errmsg("missing data for column \"%s\"",
-								NameStr(att->attname))));
+								attr->name)));
 			string = field_strings[fieldno++];
 
 			if (cstate->convert_select_flags &&
@@ -3718,14 +3789,39 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 				}
 			}
 
-			cstate->cur_attname = NameStr(att->attname);
+			cstate->cur_attname = attr->name;
 			cstate->cur_attval = string;
-			values[m] = InputFunctionCall(&in_functions[m],
-										  string,
-										  typioparams[m],
-										  att->atttypmod);
-			if (string != NULL)
-				nulls[m] = false;
+
+			if (string == NULL && fcinfo->flinfo->fn_strict)
+				nulls[m] = true;
+			else
+			{
+				fcinfo->args[0].value = CStringGetDatum(string);
+				fcinfo->args[0].isnull = false;
+				fcinfo->args[1].value = ObjectIdGetDatum(attr->typioparam);
+				fcinfo->args[1].isnull = false;
+				fcinfo->args[2].value = Int32GetDatum(attr->typmod);
+				fcinfo->args[2].isnull = false;
+
+				values[m] = FunctionCallInvoke(fcinfo);
+
+				/* Should get null result if and only if str is NULL */
+				if (string == NULL)
+				{
+					if (unlikely(!fcinfo->isnull))
+						elog(ERROR, "input function %u returned non-NULL",
+							 fcinfo->flinfo->fn_oid);
+				}
+				else
+				{
+					if (unlikely(fcinfo->isnull))
+						elog(ERROR, "input function %u returned NULL",
+							 fcinfo->flinfo->fn_oid);
+				}
+
+				nulls[m] = string == NULL;
+			}
+
 			cstate->cur_attname = NULL;
 			cstate->cur_attval = NULL;
 		}
@@ -3787,9 +3883,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 			i++;
 			values[m] = CopyReadBinaryAttribute(cstate,
 												i,
-												&in_functions[m],
-												typioparams[m],
-												att->atttypmod,
+												&in_attributes[m],
 												&nulls[m]);
 			cstate->cur_attname = NULL;
 		}
@@ -4714,13 +4808,13 @@ endfield:
  * Read a binary attribute
  */
 static Datum
-CopyReadBinaryAttribute(CopyState cstate,
-						int column_no, FmgrInfo *flinfo,
-						Oid typioparam, int32 typmod,
+CopyReadBinaryAttribute(CopyState cstate, int column_no,
+						CopyInAttributeInfo *attr,
 						bool *isnull)
 {
 	int32		fld_size;
 	Datum		result;
+	FunctionCallInfo fcinfo = &attr->in_fcinfo.fcinfo;
 
 	if (!CopyGetInt32(cstate, &fld_size))
 		ereport(ERROR,
@@ -4729,7 +4823,7 @@ CopyReadBinaryAttribute(CopyState cstate,
 	if (fld_size == -1)
 	{
 		*isnull = true;
-		return ReceiveFunctionCall(flinfo, NULL, typioparam, typmod);
+		return ReceiveFunctionCall(fcinfo->flinfo, NULL, attr->typioparam, attr->typmod);
 	}
 	if (fld_size < 0)
 		ereport(ERROR,
@@ -4750,8 +4844,18 @@ CopyReadBinaryAttribute(CopyState cstate,
 	cstate->attribute_buf.data[fld_size] = '\0';
 
 	/* Call the column type's binary input converter */
-	result = ReceiveFunctionCall(flinfo, &cstate->attribute_buf,
-								 typioparam, typmod);
+	fcinfo->args[0].value = PointerGetDatum(&cstate->attribute_buf);
+	fcinfo->args[0].isnull = false;
+	fcinfo->args[1].value = ObjectIdGetDatum(attr->typioparam);
+	fcinfo->args[1].isnull = false;
+	fcinfo->args[2].value = Int32GetDatum(attr->typmod);
+	fcinfo->args[2].isnull = false;
+
+	result = FunctionCallInvoke(fcinfo);
+
+	if (unlikely(fcinfo->isnull))
+		elog(ERROR, "receive function %u returned NULL",
+			 fcinfo->flinfo->fn_oid);
 
 	/* Trouble if it didn't eat the whole buffer */
 	if (cstate->attribute_buf.cursor != cstate->attribute_buf.len)
-- 
2.25.0.114.g5b0ca878e0

>From 6a569ceade530fcbc3f4f18bae003d118239e169 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 2 Jun 2020 17:48:04 -0700
Subject: [PATCH v2 8/9] inline pq_sendbytes, stop maintaining trailing null
 byte.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/include/libpq/pqformat.h | 13 ++++++++++++-
 src/backend/libpq/pqformat.c | 11 -----------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index e0dbb783c6a..6af153b7f77 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -22,7 +22,6 @@ extern void pq_beginmessage_reuse(StringInfo buf, char msgtype);
 extern void pq_endmessage(StringInfo buf);
 extern void pq_endmessage_reuse(StringInfo buf);
 
-extern void pq_sendbytes(StringInfo buf, const char *data, int datalen);
 extern void pq_sendcountedtext(StringInfo buf, const char *str, int slen,
 							   bool countincludesself);
 extern void pq_sendtext(StringInfo buf, const char *str, int slen);
@@ -215,6 +214,18 @@ pq_sendbyte(StringInfo buf, uint8 byt)
 	pq_sendint8(buf, byt);
 }
 
+static inline void
+pq_sendbytes(StringInfo buf, const char *data, int datalen)
+{
+	/*
+	 * FIXME: used to say:
+	 * use variant that maintains a trailing null-byte, out of caution but
+	 * that doesn't make much sense to me, and proves to be a performance
+	 * issue.
+	 */
+	appendBinaryStringInfoNT(buf, data, datalen);
+}
+
 /*
  * Append a binary integer to a StringInfo buffer
  *
diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c
index 347a0aa697a..377335a94ae 100644
--- a/src/backend/libpq/pqformat.c
+++ b/src/backend/libpq/pqformat.c
@@ -117,17 +117,6 @@ pq_beginmessage_reuse(StringInfo buf, char msgtype)
 	buf->cursor = msgtype;
 }
 
-/* --------------------------------
- *		pq_sendbytes	- append raw data to a StringInfo buffer
- * --------------------------------
- */
-void
-pq_sendbytes(StringInfo buf, const char *data, int datalen)
-{
-	/* use variant that maintains a trailing null-byte, out of caution */
-	appendBinaryStringInfo(buf, data, datalen);
-}
-
 /* --------------------------------
  *		pq_sendcountedtext - append a counted text string (with character set conversion)
  *
-- 
2.25.0.114.g5b0ca878e0

>From 7b9913519e51a683059193261c8630eb953b3b99 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 2 Jun 2020 17:48:43 -0700
Subject: [PATCH v2 9/9] heavy-wip: Allow string buffer reuse in send
 functions.

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/include/libpq/pqformat.h         | 30 +++++++++++++---------------
 src/backend/access/common/printtup.c |  9 +++++++--
 src/backend/commands/copy.c          |  5 ++++-
 src/backend/utils/adt/int.c          | 10 ++++++----
 src/backend/utils/adt/int8.c         |  9 +++++----
 src/backend/utils/adt/varlena.c      | 10 ++++++----
 src/backend/utils/fmgr/fmgr.c        | 16 ++++++++++++++-
 7 files changed, 57 insertions(+), 32 deletions(-)

diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index 6af153b7f77..c8ac781c726 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -39,27 +39,25 @@ static inline void
 pq_begintypsend(StringInfo buf)
 {
 	initStringInfo(buf);
-	/* Reserve four bytes for the bytea length word */
-	appendStringInfoSpaces(buf, 4);
+
+	/*
+	 * Reserve four bytes for the bytea length word.  We don't need to fill
+	 * them with anything (pq_endtypsend will do that), and this function is
+	 * enough of a hot spot that it's worth cheating to save some cycles. Note
+	 * in particular that we don't bother to guarantee that the buffer is
+	 * null-terminated.
+	 */
+	Assert(buf->maxlen > 4);
+	buf->len = 4;
 }
 
-/* --------------------------------
- *		pq_begintypsend_ex	- like pq_begintypesend, but with length hint
- *
- * This can be used over pq_begintypesend if the caller can cheaply determine
- * how much data will be sent, reducing the initial size of the
- * StringInfo. The passed in size need not include the overhead of the length
- * word.
- * --------------------------------
- */
 static inline void
-pq_begintypsend_ex(StringInfo buf, int size)
+pq_begintypsend_res(StringInfo buf)
 {
-	initStringInfoEx(buf, size + 4);
-	/* Reserve four bytes for the bytea length word */
-	appendStringInfoSpaces(buf, 4);
-}
+	Assert(buf && buf->data && buf->len == 0);
 
+	buf->len = 4;
+}
 
 /* --------------------------------
  *		pq_endtypsend	- finish constructing a bytea result
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index db23bb99c16..7065dc2110b 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -76,6 +76,7 @@ typedef struct
 	int			nattrs;
 	PrinttupAttrInfo *myinfo;	/* Cached info about each attr */
 	StringInfoData buf;			/* output buffer (*not* in tmpcontext) */
+	StringInfoData fieldbuf;	/*  */
 	MemoryContext tmpcontext;	/* Memory context for per-row workspace */
 } DR_printtup;
 
@@ -148,6 +149,8 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	 */
 	initStringInfo(&myState->buf);
 
+	initStringInfo(&myState->fieldbuf);
+
 	/*
 	 * Create a temporary memory context that we can reset once per row to
 	 * recover palloc'd memory.  This avoids any problems with leaks inside
@@ -366,7 +369,7 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 			fmgr_info(thisState->typoutput, &thisState->finfo);
 			InitFunctionCallInfoData(thisState->fcinfo_data.fcinfo,
 									 &thisState->finfo, 1, InvalidOid,
-									 NULL, NULL);
+									 (Node *) &myState->fieldbuf, NULL);
 		}
 		else if (format == 1)
 		{
@@ -376,7 +379,7 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 			fmgr_info(thisState->typsend, &thisState->finfo);
 			InitFunctionCallInfoData(thisState->fcinfo_data.fcinfo,
 									 &thisState->finfo, 1, InvalidOid,
-									 NULL, NULL);
+									 (Node *) &myState->fieldbuf, NULL);
 		}
 		else
 			ereport(ERROR,
@@ -396,6 +399,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 	DR_printtup *myState = (DR_printtup *) self;
 	MemoryContext oldcontext;
 	StringInfo	buf = &myState->buf;
+	StringInfo	fieldbuf = &myState->fieldbuf;
 	int			natts = typeinfo->natts;
 	int			i;
 
@@ -472,6 +476,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 
 			pq_sendint32(buf, outputlen);
 			pq_sendbytes(buf, VARDATA(outputbytes), outputlen);
+			resetStringInfo(fieldbuf);
 		}
 	}
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index bec1ce51260..45945c2f67f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1902,6 +1902,7 @@ BeginCopyTo(ParseState *pstate,
 
 	cstate = BeginCopy(pstate, false, rel, query, queryRelId, attnamelist,
 					   options);
+	initStringInfo(&cstate->attribute_buf);
 	oldcontext = MemoryContextSwitchTo(cstate->copycontext);
 
 	if (pipe)
@@ -2084,7 +2085,7 @@ CopyTo(CopyState cstate)
 		fmgr_info(out_func_oid, &attr->out_finfo);
 		InitFunctionCallInfoData(attr->out_fcinfo.fcinfo, &attr->out_finfo,
 								 1, InvalidOid,
-								 NULL, NULL);
+								 (Node *) &cstate->attribute_buf, NULL);
 	}
 
 	/*
@@ -2201,6 +2202,7 @@ CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 	MemoryContext oldcontext;
 	ListCell   *cur;
 	char	   *string;
+	StringInfo	attribute_buf = &cstate->attribute_buf;
 
 	MemoryContextReset(cstate->rowcontext);
 	oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
@@ -2279,6 +2281,7 @@ CopyOneRowTo(CopyState cstate, TupleTableSlot *slot)
 
 				CopySendInt32(cstate, outputlen);
 				CopySendData(cstate, VARDATA(outputbytes), outputlen);
+				resetStringInfo(attribute_buf);
 			}
 		}
 	}
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 84d22fc5b99..811527b0eb0 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -303,11 +303,13 @@ Datum
 int4send(PG_FUNCTION_ARGS)
 {
 	int32		arg1 = PG_GETARG_INT32(0);
-	StringInfoData buf;
+	StringInfo	buf;
 
-	pq_begintypsend_ex(&buf, 4);
-	pq_sendint32(&buf, arg1);
-	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+	buf = (StringInfo ) fcinfo->context;
+
+	pq_begintypsend_res(buf);
+	pq_sendint32(buf, arg1);
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
 }
 
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index b258778622d..dad0663dc58 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -173,11 +173,12 @@ Datum
 int8send(PG_FUNCTION_ARGS)
 {
 	int64		arg1 = PG_GETARG_INT64(0);
-	StringInfoData buf;
+	StringInfo	buf;
 
-	pq_begintypsend_ex(&buf, 8);
-	pq_sendint64(&buf, arg1);
-	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+	buf = (StringInfo ) fcinfo->context;
+	pq_begintypsend_res(buf);
+	pq_sendint64(buf, arg1);
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
 }
 
 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index f3c1a63455a..29edb03b49a 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -582,11 +582,13 @@ Datum
 textsend(PG_FUNCTION_ARGS)
 {
 	text	   *t = PG_GETARG_TEXT_PP(0);
-	StringInfoData buf;
+	StringInfo	buf = (StringInfo ) fcinfo->context;
 
-	pq_begintypsend(&buf);
-	pq_sendtext(&buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
-	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
+	Assert(fcinfo->context);
+
+	pq_begintypsend_res(buf);
+	pq_sendtext(buf, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
 }
 
 
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 03c614b234a..13483b09881 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -1637,7 +1637,21 @@ ReceiveFunctionCall(FmgrInfo *flinfo, StringInfo buf,
 bytea *
 SendFunctionCall(FmgrInfo *flinfo, Datum val)
 {
-	return DatumGetByteaP(FunctionCall1(flinfo, val));
+	StringInfoData buf;
+	Datum result;
+	LOCAL_FCINFO(fcinfo, 1);
+
+	initStringInfo(&buf);
+
+	InitFunctionCallInfoData(*fcinfo, flinfo, 1, InvalidOid, (Node *) &buf, NULL);
+	fcinfo->args[0].value = val;
+	fcinfo->args[0].isnull = false;
+	result = FunctionCallInvoke(fcinfo);
+
+	if (fcinfo->isnull)
+		elog(ERROR, "function %u returned NULL", flinfo->fn_oid);
+
+	return DatumGetByteaP(result);
 }
 
 /*
-- 
2.25.0.114.g5b0ca878e0

Reply via email to