SendRowDescriptionMessage() is slow for queries with a lot of columns
Hi,
When running workloads that include fast queries with a lot of columns,
SendRowDescriptionMessage(), and the routines it calls, becomes a
bottleneck. Besides syscache lookups (see [1]http://archives.postgresql.org/message-id/CA+Tgmobj72E_tG6w98H0oUbCCUmoC4uRmjocYPbnWC2RxYACeg@mail.gmail.com and [2]http://archives.postgresql.org/message-id/20170914061207.zxotvyopetm7lrrp%40alap3.anarazel.de) a major cost of
that is manipulation of the StringBuffer and the version specific
branches in the per-attribute loop. As so often, the performance
differential of this patch gets bigger when the other performance
patches are applied.
The issues in SendRowDescriptionMessage() are the following:
1) All the pq_sendint calls, and also the pq_sendstring, are
expensive. The amount of calculations done for a single 2/4 byte
addition to the stringbuffer (particularly enlargeStringInfo()) is
problematic, as are the reallocations themselves.
I addressed this by adding pq_send*_pre() wrappers, implemented as
inline functions, that require that memory is pre-allocated.
Combining that with doing a enlargeStringInfo() in
SendRowDescriptionMessage() that pre-allocates the maximum required
memory, yields pretty good speedup.
I'm not yet super sure about the implementation. For one, I'm not
sure this shouldn't instead be stringinfo.h functions, with very very
tiny pqformat.h wrappers. But conversely I think it'd make a lot of
sense for the pqformat integer functions to get rid of the
continually maintained trailing null-byte - I was hoping the compiler
could optimize that away, but alas, no luck. As soon as a single
integer is sent, you can't rely on 0 terminated strings anyway.
2) It creates a new StringInfo in every iteration. That causes
noticeable memory management overhead, and restarts the size of the
buffer every time. When the description is relatively large, that
leads to a number of reallocations for every
SendRowDescriptionMessage() call.
My solution here was to create persistent StringInfo for
SendRowDescriptionMessage() that never gets deallocated (just
reset). That in combination with new versions of
pq_beginmessage/endmessage that keep the buffer alive, yields a nice
speedup.
Currently I'm using a static variable to allocate a string buffer for
the function. It'd probably better to manage that outside of a single
function - I'm also wondering why we're allocating a good number of
stringinfos in various places, rather than reuse them. Most of them
can't be entered recursively, and even if that's a concern, we could
have one reusable per portal or such.
3) The v2/v3 branches in the attribute loop are noticeable (others too,
but well...). I solved that by splitting out the v2 and v3
per-attribute loops into separate functions. Imo also a good chunk
more readable.
Comments?
Greetings,
Andres Freund
[1]: http://archives.postgresql.org/message-id/CA+Tgmobj72E_tG6w98H0oUbCCUmoC4uRmjocYPbnWC2RxYACeg@mail.gmail.com
[2]: http://archives.postgresql.org/message-id/20170914061207.zxotvyopetm7lrrp%40alap3.anarazel.de
Attachments:
0002-Add-more-efficient-functions-to-pqformat-API.patchtext/x-diff; charset=us-asciiDownload
From 672cbfd0660e4d2b2cc6980f3f5c2af27e692404 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 18:39:24 -0700
Subject: [PATCH 2/8] Add more efficient functions to pqformat API.
New inline functions allow to add data to a stringbuf in a more
efficient manner by pre-allocating ahead of time, and
pq_beginmessage_pre/pq_endmessage_keep allow reuse of a stringbuffer.
---
src/backend/libpq/pqformat.c | 37 +++++++++++++++++++++++++++++++++
src/backend/utils/mb/mbutils.c | 11 ----------
src/include/libpq/pqformat.h | 47 ++++++++++++++++++++++++++++++++++++++++++
src/include/mb/pg_wchar.h | 11 ++++++++++
4 files changed, 95 insertions(+), 11 deletions(-)
diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c
index c8cf67c041..6e40ee087c 100644
--- a/src/backend/libpq/pqformat.c
+++ b/src/backend/libpq/pqformat.c
@@ -97,6 +97,28 @@ pq_beginmessage(StringInfo buf, char msgtype)
buf->cursor = msgtype;
}
+/* --------------------------------
+
+ * pq_beginmessage_pre - initialize for sending a message, reuse buffer
+ *
+ * This requires the buffer to be allocated in an sufficiently long-lived
+ * memory context.
+ * --------------------------------
+ */
+void
+pq_beginmessage_pre(StringInfo buf, char msgtype)
+{
+ resetStringInfo(buf);
+
+ /*
+ * We stash the message type into the buffer's cursor field, expecting
+ * that the pq_sendXXX routines won't touch it. We could alternatively
+ * make it the first byte of the buffer contents, but this seems easier.
+ */
+ buf->cursor = msgtype;
+}
+
+
/* --------------------------------
* pq_sendbyte - append a raw byte to a StringInfo buffer
* --------------------------------
@@ -350,6 +372,21 @@ pq_endmessage(StringInfo buf)
buf->data = NULL;
}
+/* --------------------------------
+ * pq_endmessage_keep - send the completed message to the frontend
+ *
+ * The data buffer is *not* freed, allowing to reuse the buffer with
+ * pg_beginmessage_pre.
+ --------------------------------
+ */
+
+void
+pq_endmessage_keep(StringInfo buf)
+{
+ /* msgtype was saved in cursor field */
+ (void) pq_putmessage(buf->cursor, buf->data, buf->len);
+}
+
/* --------------------------------
* pq_begintypsend - initialize for constructing a bytea result
diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c
index c4fbe0903b..56f4dc1453 100644
--- a/src/backend/utils/mb/mbutils.c
+++ b/src/backend/utils/mb/mbutils.c
@@ -41,17 +41,6 @@
#include "utils/memutils.h"
#include "utils/syscache.h"
-/*
- * When converting strings between different encodings, we assume that space
- * for converted result is 4-to-1 growth in the worst case. The rate for
- * currently supported encoding pairs are within 3 (SJIS JIS X0201 half width
- * kanna -> UTF8 is the worst case). So "4" should be enough for the moment.
- *
- * Note that this is not the same as the maximum character width in any
- * particular encoding.
- */
-#define MAX_CONVERSION_GROWTH 4
-
/*
* We maintain a simple linked list caching the fmgr lookup info for the
* currently selected conversion functions, as well as any that have been
diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index 32112547a0..2e5b2b7685 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -14,8 +14,10 @@
#define PQFORMAT_H
#include "lib/stringinfo.h"
+#include "mb/pg_wchar.h"
extern void pq_beginmessage(StringInfo buf, char msgtype);
+extern void pq_beginmessage_pre(StringInfo buf, char msgtype);
extern void pq_sendbyte(StringInfo buf, int byt);
extern void pq_sendbytes(StringInfo buf, const char *data, int datalen);
extern void pq_sendcountedtext(StringInfo buf, const char *str, int slen,
@@ -28,6 +30,51 @@ extern void pq_sendint64(StringInfo buf, int64 i);
extern void pq_sendfloat4(StringInfo buf, float4 f);
extern void pq_sendfloat8(StringInfo buf, float8 f);
extern void pq_endmessage(StringInfo buf);
+extern void pq_endmessage_keep(StringInfo buf);
+
+extern void pq_sendint64(StringInfo buf, int64 i);
+extern void pq_sendfloat4(StringInfo buf, float4 f);
+extern void pq_sendfloat8(StringInfo buf, float8 f);
+
+/* inline versions that require all space is pre-allocated */
+static inline void
+pq_sendint32_pre(StringInfo buf, int32 i)
+{
+ Assert(buf->len + sizeof(i) + 1 <= buf->maxlen);
+ *(int32* ) (buf->data + buf->len) = htonl(i);
+ buf->len += sizeof(i);
+ *(char *) (buf->data + buf->len) = '\0';
+}
+
+static inline void
+pq_sendint16_pre(StringInfo buf, int16 i)
+{
+ Assert(buf->len + sizeof(i) + 1 <= buf->maxlen);
+ *(int16* ) (buf->data + buf->len) = htons(i);
+ buf->len += sizeof(i);
+ *(char *) (buf->data + buf->len) = '\0';
+}
+
+static inline void
+pq_sendstring_pre(StringInfo buf, const char *str)
+{
+ int slen = strlen(str);
+ char *p;
+
+ p = pg_server_to_client(str, slen);
+ if (p != str) /* actual conversion has been done? */
+ slen = strlen(p);
+
+ Assert(buf->len + slen + 1 <= buf->maxlen);
+
+ memcpy(buf->data + buf->len, p, slen + 1);
+ buf->len += slen + 1;
+ *(char *) (buf->data + buf->len) = '\0';
+
+ if (p != str)
+ pfree(p);
+}
+
extern void pq_begintypsend(StringInfo buf);
extern bytea *pq_endtypsend(StringInfo buf);
diff --git a/src/include/mb/pg_wchar.h b/src/include/mb/pg_wchar.h
index d57ef017cb..9227d634f6 100644
--- a/src/include/mb/pg_wchar.h
+++ b/src/include/mb/pg_wchar.h
@@ -304,6 +304,17 @@ typedef enum pg_enc
/* On FE are possible all encodings */
#define PG_VALID_FE_ENCODING(_enc) PG_VALID_ENCODING(_enc)
+/*
+ * When converting strings between different encodings, we assume that space
+ * for converted result is 4-to-1 growth in the worst case. The rate for
+ * currently supported encoding pairs are within 3 (SJIS JIS X0201 half width
+ * kanna -> UTF8 is the worst case). So "4" should be enough for the moment.
+ *
+ * Note that this is not the same as the maximum character width in any
+ * particular encoding.
+ */
+#define MAX_CONVERSION_GROWTH 4
+
/*
* Table for mapping an encoding number to official encoding name and
* possibly other subsidiary data. Be careful to check encoding number
--
2.14.1.536.g6867272d5b.dirty
0003-Improve-performance-of-SendRowDescriptionMessage.patchtext/x-diff; charset=us-asciiDownload
From 4b369d2aa88f0fed53e304932d39defc95b9bbfb Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 18:41:50 -0700
Subject: [PATCH 3/8] Improve performance of SendRowDescriptionMessage.
Using the new pqformat functions yields performance for statements
with a noticeable number of rows. The function itself is more than
twice as fast.
---
src/backend/access/common/printtup.c | 156 ++++++++++++++++++++++++++---------
1 file changed, 118 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 20d20e623e..894f89b63c 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
@@ -189,12 +191,118 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
{
int natts = typeinfo->natts;
int proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
+ static StringInfo rowDescriptionBuf = NULL;
+
+ /*
+ * As this routine is executed for every single query, it can be a
+ * pq_sendstring_prebottleneck. To avoid unnecessary allocator overhead
+ * reuse a single buffer. That means we'll never shrink below the largest
+ * row-description sent, but that seems acceptable given the limited size.
+ */
+ if (unlikely(!rowDescriptionBuf))
+ {
+ MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
+ rowDescriptionBuf = makeStringInfo();
+ MemoryContextSwitchTo(oldContext);
+ }
+
+ /* tuple descriptor message type */
+ pq_beginmessage_pre(rowDescriptionBuf, 'T');
+ /* # of attrs in tuples */
+ pq_sendint(rowDescriptionBuf, natts, 2);
+
+ if (proto >= 3)
+ SendRowDescriptionCols_3(rowDescriptionBuf, typeinfo, targetlist,
+ formats);
+ else
+ SendRowDescriptionCols_2(rowDescriptionBuf, typeinfo, targetlist,
+ formats);
+
+ pq_endmessage_keep(rowDescriptionBuf);
+}
+
+/*
+ * 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 */
+ /*
+ * Prealloc 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_sendstring_pre(buf, NameStr(att->attname));
+ pq_sendint32_pre(buf, resorigtbl);
+ pq_sendint16_pre(buf, resorigcol);
+ pq_sendint32_pre(buf, atttypid);
+ pq_sendint16_pre(buf, att->attlen);
+ pq_sendint32_pre(buf, atttypmod);
+ pq_sendint16_pre(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)
{
@@ -202,44 +310,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_sendint(buf, (int) atttypid, sizeof(atttypid));
+ pq_sendint(buf, att->attlen, sizeof(att->attlen));
+ pq_sendint(buf, atttypmod, sizeof(atttypmod));
+ /* format info only appears in protocol 3.0 and up */
}
- pq_endmessage(&buf);
}
/*
--
2.14.1.536.g6867272d5b.dirty
On 14 September 2017 at 07:34, Andres Freund <andres@anarazel.de> wrote:
Hi,
When running workloads that include fast queries with a lot of columns,
SendRowDescriptionMessage(), and the routines it calls, becomes a
bottleneck. Besides syscache lookups (see [1] and [2]) a major cost of
that is manipulation of the StringBuffer and the version specific
branches in the per-attribute loop. As so often, the performance
differential of this patch gets bigger when the other performance
patches are applied.The issues in SendRowDescriptionMessage() are the following:
1) All the pq_sendint calls, and also the pq_sendstring, are
expensive. The amount of calculations done for a single 2/4 byte
addition to the stringbuffer (particularly enlargeStringInfo()) is
problematic, as are the reallocations themselves.I addressed this by adding pq_send*_pre() wrappers, implemented as
inline functions, that require that memory is pre-allocated.
Combining that with doing a enlargeStringInfo() in
SendRowDescriptionMessage() that pre-allocates the maximum required
memory, yields pretty good speedup.I'm not yet super sure about the implementation. For one, I'm not
sure this shouldn't instead be stringinfo.h functions, with very very
tiny pqformat.h wrappers. But conversely I think it'd make a lot of
sense for the pqformat integer functions to get rid of the
continually maintained trailing null-byte - I was hoping the compiler
could optimize that away, but alas, no luck. As soon as a single
integer is sent, you can't rely on 0 terminated strings anyway.2) It creates a new StringInfo in every iteration. That causes
noticeable memory management overhead, and restarts the size of the
buffer every time. When the description is relatively large, that
leads to a number of reallocations for every
SendRowDescriptionMessage() call.My solution here was to create persistent StringInfo for
SendRowDescriptionMessage() that never gets deallocated (just
reset). That in combination with new versions of
pq_beginmessage/endmessage that keep the buffer alive, yields a nice
speedup.Currently I'm using a static variable to allocate a string buffer for
the function. It'd probably better to manage that outside of a single
function - I'm also wondering why we're allocating a good number of
stringinfos in various places, rather than reuse them. Most of them
can't be entered recursively, and even if that's a concern, we could
have one reusable per portal or such.3) The v2/v3 branches in the attribute loop are noticeable (others too,
but well...). I solved that by splitting out the v2 and v3
per-attribute loops into separate functions. Imo also a good chunk
more readable.Comments?
I've run a fairly basic test with a table with 101 columns, selecting
a single row from the table and I get the following results:
Columns with 1-character names:
master (80 jobs, 80 connections, 60 seconds):
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 60 s
number of transactions actually processed: 559275
latency average = 8.596 ms
tps = 9306.984058 (including connections establishing)
tps = 11144.622096 (excluding connections establishing)
patched:
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 60 s
number of transactions actually processed: 585526
latency average = 8.210 ms
tps = 9744.240847 (including connections establishing)
tps = 11454.339301 (excluding connections establishing)
master (80 jobs, 80 connections, 120 seconds):
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 120 s
number of transactions actually processed: 1108312
latency average = 8.668 ms
tps = 9229.356994 (including connections establishing)
tps = 9987.385281 (excluding connections establishing)
patched:
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 120 s
number of transactions actually processed: 1130313
latency average = 8.499 ms
tps = 9412.904876 (including connections establishing)
tps = 10319.404302 (excluding connections establishing)
Columns with at least 42-character names:
master (80 jobs, 80 connections, 60 seconds):
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 60 s
number of transactions actually processed: 197815
latency average = 24.308 ms
tps = 3291.157486 (including connections establishing)
tps = 3825.309489 (excluding connections establishing)
patched:
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 60 s
number of transactions actually processed: 198549
latency average = 24.214 ms
tps = 3303.896651 (including connections establishing)
tps = 3895.738024 (excluding connections establishing)
master (80 jobs, 80 connections, 120 seconds):
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 120 s
number of transactions actually processed: 391312
latency average = 24.551 ms
tps = 3258.493026 (including connections establishing)
tps = 3497.581844 (excluding connections establishing)
patched:
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
number of clients: 80
number of threads: 80
duration: 120 s
number of transactions actually processed: 391733
latency average = 24.526 ms
tps = 3261.805060 (including connections establishing)
tps = 3552.604408 (excluding connections establishing)
This is just using the patches you posted on this thread. Does this
exercise the patch in the way you intended?
Regards
Thom
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Thom,
Thanks for taking a whack at this!
On 2017-09-15 12:16:22 +0100, Thom Brown wrote:
I've run a fairly basic test with a table with 101 columns, selecting
a single row from the table and I get the following results:Columns with 1-character names:
master (80 jobs, 80 connections, 60 seconds):
FWIW, I don't think it's useful to test this with a lot of concurrency -
at that point you're likely saturating the machine with context switches
etc. unless you have a lot of cores. As this is isn't related to
concurrency I'd rather just check a single connection.
transaction type: /tmp/test.sql
scaling factor: 1
query mode: simple
I think you'd need to use prepared statements / -M prepared to see
benefits - when parsing statements for every execution the bottleneck is
elsewhere (hello O(#available_columns * #selected_columns) in
colNameToVar()).
Greetings,
Andres Freund
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 15 September 2017 at 19:23, Andres Freund <andres@anarazel.de> wrote:
Hi Thom,
Thanks for taking a whack at this!
On 2017-09-15 12:16:22 +0100, Thom Brown wrote:
I've run a fairly basic test with a table with 101 columns, selecting
a single row from the table and I get the following results:Columns with 1-character names:
master (80 jobs, 80 connections, 60 seconds):
FWIW, I don't think it's useful to test this with a lot of concurrency -
at that point you're likely saturating the machine with context switches
etc. unless you have a lot of cores. As this is isn't related to
concurrency I'd rather just check a single connection.transaction type: /tmp/test.sql
scaling factor: 1
query mode: simpleI think you'd need to use prepared statements / -M prepared to see
benefits - when parsing statements for every execution the bottleneck is
elsewhere (hello O(#available_columns * #selected_columns) in
colNameToVar()).
Okay, I've retested it using a prepared statement executed 100,000
times (which selects a single row from a table with 101 columns, each
column is 42-43 characters long, and 2 rows in the table), and I get
the following:
master:
real 1m23.485s
user 1m2.800s
sys 0m1.200s
patched:
real 1m22.530s
user 1m2.860s
sys 0m1.140s
Not sure why I'm not seeing the gain.
Thom
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Thom,
On 2017-09-15 22:05:35 +0100, Thom Brown wrote:
Okay, I've retested it using a prepared statement executed 100,000
times (which selects a single row from a table with 101 columns, each
column is 42-43 characters long, and 2 rows in the table), and I get
the following:master:
real 1m23.485s
user 1m2.800s
sys 0m1.200spatched:
real 1m22.530s
user 1m2.860s
sys 0m1.140sNot sure why I'm not seeing the gain.
I suspect a part of that is that you're testing the patch in isolation,
whereas I tested it as part of multiple speedup patches. There's some
bigger bottlenecks than this one. I've attached my whole stack.
But even that being said, here's the result for these patches in
isolation on my machine:
setup:
psql -p 5440 -f ~/tmp/create_many_cols.sql
pgbench -M prepared -f ~/tmp/pgbench-many-cols.sql -n -T 10 -P 1
master (best of three):
tps = 13347.023301 (excluding connections establishing)
patched (best of three):
tps = 14309.690741 (excluding connections establishing)
Which is a bigger win than what you're observing. I've also attached the
benchmark scripts I used. Could you detail how your benchmark works a
bit more? Any chance you looped in plpgsql or such?
Just for fun/reference, these are the results with all the patches
applied:
tps = 19069.115553 (excluding connections establishing)
and with just this patch reverted:
tps = 17342.006825 (excluding connections establishing)
Regards,
Andres
Attachments:
0001-Speedup-pgstat_report_activity-by-moving-mb-aware-v1.patchtext/x-diff; charset=us-asciiDownload
From a2325a2649da403dc562b8e93df972839823d924 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 19:25:34 -0700
Subject: [PATCH 1/8] Speedup pgstat_report_activity by moving mb-aware
truncation to read side.
Previously multi-byte aware truncation was done on every
pgstat_report_activity() call - proving to be a bottleneck for
workloads with long query strings that execute quickly.
Instead move the truncation to the read side, which is commonly
executed far less frequently. That's possible because all server
encodings allow to determine the length of a multi-byte string from
the first byte.
Author: Andres Freund
Discussion: https://postgr.es/m/20170912071948.pa7igbpkkkviecpz@alap3.anarazel.de
---
src/backend/postmaster/pgstat.c | 63 ++++++++++++++++++++++++++++---------
src/backend/utils/adt/pgstatfuncs.c | 17 +++++++---
src/include/pgstat.h | 12 +++++--
3 files changed, 72 insertions(+), 20 deletions(-)
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index accf302cf7..1ffdac5448 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2701,7 +2701,7 @@ CreateSharedBackendStatus(void)
buffer = BackendActivityBuffer;
for (i = 0; i < NumBackendStatSlots; i++)
{
- BackendStatusArray[i].st_activity = buffer;
+ BackendStatusArray[i].st_activity_raw = buffer;
buffer += pgstat_track_activity_query_size;
}
}
@@ -2922,11 +2922,11 @@ pgstat_bestart(void)
#endif
beentry->st_state = STATE_UNDEFINED;
beentry->st_appname[0] = '\0';
- beentry->st_activity[0] = '\0';
+ beentry->st_activity_raw[0] = '\0';
/* Also make sure the last byte in each string area is always 0 */
beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
beentry->st_appname[NAMEDATALEN - 1] = '\0';
- beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
+ beentry->st_activity_raw[pgstat_track_activity_query_size - 1] = '\0';
beentry->st_progress_command = PROGRESS_COMMAND_INVALID;
beentry->st_progress_command_target = InvalidOid;
@@ -3017,7 +3017,7 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
pgstat_increment_changecount_before(beentry);
beentry->st_state = STATE_DISABLED;
beentry->st_state_start_timestamp = 0;
- beentry->st_activity[0] = '\0';
+ beentry->st_activity_raw[0] = '\0';
beentry->st_activity_start_timestamp = 0;
/* st_xact_start_timestamp and wait_event_info are also disabled */
beentry->st_xact_start_timestamp = 0;
@@ -3034,8 +3034,12 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
start_timestamp = GetCurrentStatementStartTimestamp();
if (cmd_str != NULL)
{
- len = pg_mbcliplen(cmd_str, strlen(cmd_str),
- pgstat_track_activity_query_size - 1);
+ /*
+ * Compute length of to-be-stored string unaware of multi-byte
+ * characters. For speed reasons that'll get corrected on read, rather
+ * than computed every write.
+ */
+ len = Min(strlen(cmd_str), pgstat_track_activity_query_size - 1);
}
current_timestamp = GetCurrentTimestamp();
@@ -3049,8 +3053,8 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
if (cmd_str != NULL)
{
- memcpy((char *) beentry->st_activity, cmd_str, len);
- beentry->st_activity[len] = '\0';
+ memcpy((char *) beentry->st_activity_raw, cmd_str, len);
+ beentry->st_activity_raw[len] = '\0';
beentry->st_activity_start_timestamp = start_timestamp;
}
@@ -3278,8 +3282,8 @@ pgstat_read_current_status(void)
*/
strcpy(localappname, (char *) beentry->st_appname);
localentry->backendStatus.st_appname = localappname;
- strcpy(localactivity, (char *) beentry->st_activity);
- localentry->backendStatus.st_activity = localactivity;
+ strcpy(localactivity, (char *) beentry->st_activity_raw);
+ localentry->backendStatus.st_activity_raw = localactivity;
localentry->backendStatus.st_ssl = beentry->st_ssl;
#ifdef USE_SSL
if (beentry->st_ssl)
@@ -3945,10 +3949,13 @@ pgstat_get_backend_current_activity(int pid, bool checkUser)
/* Now it is safe to use the non-volatile pointer */
if (checkUser && !superuser() && beentry->st_userid != GetUserId())
return "<insufficient privilege>";
- else if (*(beentry->st_activity) == '\0')
+ else if (*(beentry->st_activity_raw) == '\0')
return "<command string not enabled>";
else
- return beentry->st_activity;
+ {
+ /* this'll leak a bit of memory, but that seems acceptable */
+ return pgstat_clip_activity(beentry->st_activity_raw);
+ }
}
beentry++;
@@ -3994,7 +4001,7 @@ pgstat_get_crashed_backend_activity(int pid, char *buffer, int buflen)
if (beentry->st_procpid == pid)
{
/* Read pointer just once, so it can't change after validation */
- const char *activity = beentry->st_activity;
+ const char *activity = beentry->st_activity_raw;
const char *activity_last;
/*
@@ -4017,7 +4024,8 @@ pgstat_get_crashed_backend_activity(int pid, char *buffer, int buflen)
/*
* Copy only ASCII-safe characters so we don't run into encoding
* problems when reporting the message; and be sure not to run off
- * the end of memory.
+ * the end of memory. As only ASCII characters are reported, it
+ * doesn't seem necessary to perform multibyte aware clipping.
*/
ascii_safe_strlcpy(buffer, activity,
Min(buflen, pgstat_track_activity_query_size));
@@ -6270,3 +6278,30 @@ pgstat_db_requested(Oid databaseid)
return false;
}
+
+/*
+ * Convert a potentially unsafely truncated activity string (see
+ * PgBackendStatus.st_activity_raw's documentation) into a correctly truncated
+ * one.
+ *
+ * The returned string is allocated in the caller's memory context and may be
+ * freed.
+ */
+char *
+pgstat_clip_activity(const char *activity)
+{
+ int rawlen = strnlen(activity, pgstat_track_activity_query_size - 1);
+ int cliplen;
+
+ /*
+ * All supported server-encodings make it possible to determine the length
+ * of a multi-byte character from its first byte (this is not the case for
+ * client encodings, see GB18030). As st_activity is always stored using
+ * server encoding, this allows us to perform multi-byte aware truncation,
+ * even if the string earlier was truncated in the middle of a multi-byte
+ * character.
+ */
+ cliplen = pg_mbcliplen(activity, rawlen,
+ pgstat_track_activity_query_size - 1);
+ return pnstrdup(activity, cliplen);
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 20ce48b2d8..5a968e3758 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -664,6 +664,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS))
{
SockAddr zero_clientaddr;
+ char *clipped_activity;
switch (beentry->st_state)
{
@@ -690,7 +691,9 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
break;
}
- values[5] = CStringGetTextDatum(beentry->st_activity);
+ clipped_activity = pgstat_clip_activity(beentry->st_activity_raw);
+ values[5] = CStringGetTextDatum(clipped_activity);
+ pfree(clipped_activity);
proc = BackendPidGetProc(beentry->st_procpid);
if (proc != NULL)
@@ -906,17 +909,23 @@ pg_stat_get_backend_activity(PG_FUNCTION_ARGS)
int32 beid = PG_GETARG_INT32(0);
PgBackendStatus *beentry;
const char *activity;
+ char *clipped_activity;
+ text *ret;
if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL)
activity = "<backend information not available>";
else if (!has_privs_of_role(GetUserId(), beentry->st_userid))
activity = "<insufficient privilege>";
- else if (*(beentry->st_activity) == '\0')
+ else if (*(beentry->st_activity_raw) == '\0')
activity = "<command string not enabled>";
else
- activity = beentry->st_activity;
+ activity = beentry->st_activity_raw;
- PG_RETURN_TEXT_P(cstring_to_text(activity));
+ clipped_activity = pgstat_clip_activity(activity);
+ ret = cstring_to_text(activity);
+ pfree(clipped_activity);
+
+ PG_RETURN_TEXT_P(ret);
}
Datum
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 57ac5d41e4..52af0aa541 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -1003,8 +1003,14 @@ typedef struct PgBackendStatus
/* application name; MUST be null-terminated */
char *st_appname;
- /* current command string; MUST be null-terminated */
- char *st_activity;
+ /*
+ * Current command string; MUST be null-terminated. Note that this string
+ * possibly is truncated in the middle of a multi-byte character. As
+ * activity strings are stored more frequently than read, that allows to
+ * move the cost of correct truncation to the display side. Use
+ * pgstat_clip_activity() to truncate correctly.
+ */
+ char *st_activity_raw;
/*
* Command progress reporting. Any command which wishes can advertise
@@ -1193,6 +1199,8 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
extern void pgstat_initstats(Relation rel);
+extern char *pgstat_clip_activity(const char *activity);
+
/* ----------
* pgstat_report_wait_start() -
*
--
2.14.1.536.g6867272d5b.dirty
0002-Add-more-efficient-functions-to-pqformat-APIv1.patchtext/x-diff; charset=us-asciiDownload
From 35d5ed7cb9149a9d02829204fdc52ff9437b36a9 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 18:39:24 -0700
Subject: [PATCH 2/8] Add more efficient functions to pqformat API.
New inline functions allow to add data to a stringbuf in a more
efficient manner by pre-allocating ahead of time, and
pq_beginmessage_pre/pq_endmessage_keep allow reuse of a stringbuffer.
---
src/backend/libpq/pqformat.c | 37 +++++++++++++++++++++++++++++++++
src/backend/utils/mb/mbutils.c | 11 ----------
src/include/libpq/pqformat.h | 47 ++++++++++++++++++++++++++++++++++++++++++
src/include/mb/pg_wchar.h | 11 ++++++++++
4 files changed, 95 insertions(+), 11 deletions(-)
diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c
index c8cf67c041..6e40ee087c 100644
--- a/src/backend/libpq/pqformat.c
+++ b/src/backend/libpq/pqformat.c
@@ -97,6 +97,28 @@ pq_beginmessage(StringInfo buf, char msgtype)
buf->cursor = msgtype;
}
+/* --------------------------------
+
+ * pq_beginmessage_pre - initialize for sending a message, reuse buffer
+ *
+ * This requires the buffer to be allocated in an sufficiently long-lived
+ * memory context.
+ * --------------------------------
+ */
+void
+pq_beginmessage_pre(StringInfo buf, char msgtype)
+{
+ resetStringInfo(buf);
+
+ /*
+ * We stash the message type into the buffer's cursor field, expecting
+ * that the pq_sendXXX routines won't touch it. We could alternatively
+ * make it the first byte of the buffer contents, but this seems easier.
+ */
+ buf->cursor = msgtype;
+}
+
+
/* --------------------------------
* pq_sendbyte - append a raw byte to a StringInfo buffer
* --------------------------------
@@ -350,6 +372,21 @@ pq_endmessage(StringInfo buf)
buf->data = NULL;
}
+/* --------------------------------
+ * pq_endmessage_keep - send the completed message to the frontend
+ *
+ * The data buffer is *not* freed, allowing to reuse the buffer with
+ * pg_beginmessage_pre.
+ --------------------------------
+ */
+
+void
+pq_endmessage_keep(StringInfo buf)
+{
+ /* msgtype was saved in cursor field */
+ (void) pq_putmessage(buf->cursor, buf->data, buf->len);
+}
+
/* --------------------------------
* pq_begintypsend - initialize for constructing a bytea result
diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c
index c4fbe0903b..56f4dc1453 100644
--- a/src/backend/utils/mb/mbutils.c
+++ b/src/backend/utils/mb/mbutils.c
@@ -41,17 +41,6 @@
#include "utils/memutils.h"
#include "utils/syscache.h"
-/*
- * When converting strings between different encodings, we assume that space
- * for converted result is 4-to-1 growth in the worst case. The rate for
- * currently supported encoding pairs are within 3 (SJIS JIS X0201 half width
- * kanna -> UTF8 is the worst case). So "4" should be enough for the moment.
- *
- * Note that this is not the same as the maximum character width in any
- * particular encoding.
- */
-#define MAX_CONVERSION_GROWTH 4
-
/*
* We maintain a simple linked list caching the fmgr lookup info for the
* currently selected conversion functions, as well as any that have been
diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index 32112547a0..2e5b2b7685 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -14,8 +14,10 @@
#define PQFORMAT_H
#include "lib/stringinfo.h"
+#include "mb/pg_wchar.h"
extern void pq_beginmessage(StringInfo buf, char msgtype);
+extern void pq_beginmessage_pre(StringInfo buf, char msgtype);
extern void pq_sendbyte(StringInfo buf, int byt);
extern void pq_sendbytes(StringInfo buf, const char *data, int datalen);
extern void pq_sendcountedtext(StringInfo buf, const char *str, int slen,
@@ -28,6 +30,51 @@ extern void pq_sendint64(StringInfo buf, int64 i);
extern void pq_sendfloat4(StringInfo buf, float4 f);
extern void pq_sendfloat8(StringInfo buf, float8 f);
extern void pq_endmessage(StringInfo buf);
+extern void pq_endmessage_keep(StringInfo buf);
+
+extern void pq_sendint64(StringInfo buf, int64 i);
+extern void pq_sendfloat4(StringInfo buf, float4 f);
+extern void pq_sendfloat8(StringInfo buf, float8 f);
+
+/* inline versions that require all space is pre-allocated */
+static inline void
+pq_sendint32_pre(StringInfo buf, int32 i)
+{
+ Assert(buf->len + sizeof(i) + 1 <= buf->maxlen);
+ *(int32* ) (buf->data + buf->len) = htonl(i);
+ buf->len += sizeof(i);
+ *(char *) (buf->data + buf->len) = '\0';
+}
+
+static inline void
+pq_sendint16_pre(StringInfo buf, int16 i)
+{
+ Assert(buf->len + sizeof(i) + 1 <= buf->maxlen);
+ *(int16* ) (buf->data + buf->len) = htons(i);
+ buf->len += sizeof(i);
+ *(char *) (buf->data + buf->len) = '\0';
+}
+
+static inline void
+pq_sendstring_pre(StringInfo buf, const char *str)
+{
+ int slen = strlen(str);
+ char *p;
+
+ p = pg_server_to_client(str, slen);
+ if (p != str) /* actual conversion has been done? */
+ slen = strlen(p);
+
+ Assert(buf->len + slen + 1 <= buf->maxlen);
+
+ memcpy(buf->data + buf->len, p, slen + 1);
+ buf->len += slen + 1;
+ *(char *) (buf->data + buf->len) = '\0';
+
+ if (p != str)
+ pfree(p);
+}
+
extern void pq_begintypsend(StringInfo buf);
extern bytea *pq_endtypsend(StringInfo buf);
diff --git a/src/include/mb/pg_wchar.h b/src/include/mb/pg_wchar.h
index d57ef017cb..9227d634f6 100644
--- a/src/include/mb/pg_wchar.h
+++ b/src/include/mb/pg_wchar.h
@@ -304,6 +304,17 @@ typedef enum pg_enc
/* On FE are possible all encodings */
#define PG_VALID_FE_ENCODING(_enc) PG_VALID_ENCODING(_enc)
+/*
+ * When converting strings between different encodings, we assume that space
+ * for converted result is 4-to-1 growth in the worst case. The rate for
+ * currently supported encoding pairs are within 3 (SJIS JIS X0201 half width
+ * kanna -> UTF8 is the worst case). So "4" should be enough for the moment.
+ *
+ * Note that this is not the same as the maximum character width in any
+ * particular encoding.
+ */
+#define MAX_CONVERSION_GROWTH 4
+
/*
* Table for mapping an encoding number to official encoding name and
* possibly other subsidiary data. Be careful to check encoding number
--
2.14.1.536.g6867272d5b.dirty
0003-Improve-performance-of-SendRowDescriptionMessagev1.patchtext/x-diff; charset=us-asciiDownload
From 84ec3f6db7691085a364f7dae01e13776be0c6a3 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 18:41:50 -0700
Subject: [PATCH 3/8] Improve performance of SendRowDescriptionMessage.
Using the new pqformat functions yields performance for statements
with a noticeable number of rows. The function itself is more than
twice as fast.
---
src/backend/access/common/printtup.c | 156 ++++++++++++++++++++++++++---------
1 file changed, 118 insertions(+), 38 deletions(-)
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 20d20e623e..894f89b63c 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
@@ -189,12 +191,118 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
{
int natts = typeinfo->natts;
int proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
+ static StringInfo rowDescriptionBuf = NULL;
+
+ /*
+ * As this routine is executed for every single query, it can be a
+ * pq_sendstring_prebottleneck. To avoid unnecessary allocator overhead
+ * reuse a single buffer. That means we'll never shrink below the largest
+ * row-description sent, but that seems acceptable given the limited size.
+ */
+ if (unlikely(!rowDescriptionBuf))
+ {
+ MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
+ rowDescriptionBuf = makeStringInfo();
+ MemoryContextSwitchTo(oldContext);
+ }
+
+ /* tuple descriptor message type */
+ pq_beginmessage_pre(rowDescriptionBuf, 'T');
+ /* # of attrs in tuples */
+ pq_sendint(rowDescriptionBuf, natts, 2);
+
+ if (proto >= 3)
+ SendRowDescriptionCols_3(rowDescriptionBuf, typeinfo, targetlist,
+ formats);
+ else
+ SendRowDescriptionCols_2(rowDescriptionBuf, typeinfo, targetlist,
+ formats);
+
+ pq_endmessage_keep(rowDescriptionBuf);
+}
+
+/*
+ * 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 */
+ /*
+ * Prealloc 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_sendstring_pre(buf, NameStr(att->attname));
+ pq_sendint32_pre(buf, resorigtbl);
+ pq_sendint16_pre(buf, resorigcol);
+ pq_sendint32_pre(buf, atttypid);
+ pq_sendint16_pre(buf, att->attlen);
+ pq_sendint32_pre(buf, atttypmod);
+ pq_sendint16_pre(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)
{
@@ -202,44 +310,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_sendint(buf, (int) atttypid, sizeof(atttypid));
+ pq_sendint(buf, att->attlen, sizeof(att->attlen));
+ pq_sendint(buf, atttypmod, sizeof(atttypmod));
+ /* format info only appears in protocol 3.0 and up */
}
- pq_endmessage(&buf);
}
/*
--
2.14.1.536.g6867272d5b.dirty
0004-Add-inline-murmurhash32-int32-functionv1.patchtext/x-diff; charset=us-asciiDownload
From a192dbbfb7ce27545c2905c90d6d14e90d3710e1 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 18:43:46 -0700
Subject: [PATCH 4/8] Add inline murmurhash32(int32) function.
The function already existed in tidbitmap.c but more users requiring
fast hashing of 32bit ints are coming up.
---
src/backend/nodes/tidbitmap.c | 20 ++------------------
src/include/utils/hashutils.h | 18 ++++++++++++++++++
2 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/src/backend/nodes/tidbitmap.c b/src/backend/nodes/tidbitmap.c
index c4e53adb0c..01d6bc5c11 100644
--- a/src/backend/nodes/tidbitmap.c
+++ b/src/backend/nodes/tidbitmap.c
@@ -45,6 +45,7 @@
#include "nodes/tidbitmap.h"
#include "storage/lwlock.h"
#include "utils/dsa.h"
+#include "utils/hashutils.h"
/*
* The maximum number of tuples per page is not large (typically 256 with
@@ -237,30 +238,13 @@ static int tbm_comparator(const void *left, const void *right);
static int tbm_shared_comparator(const void *left, const void *right,
void *arg);
-/*
- * Simple inline murmur hash implementation for the exact width required, for
- * performance.
- */
-static inline uint32
-hash_blockno(BlockNumber b)
-{
- uint32 h = b;
-
- h ^= h >> 16;
- h *= 0x85ebca6b;
- h ^= h >> 13;
- h *= 0xc2b2ae35;
- h ^= h >> 16;
- return h;
-}
-
/* define hashtable mapping block numbers to PagetableEntry's */
#define SH_USE_NONDEFAULT_ALLOCATOR
#define SH_PREFIX pagetable
#define SH_ELEMENT_TYPE PagetableEntry
#define SH_KEY_TYPE BlockNumber
#define SH_KEY blockno
-#define SH_HASH_KEY(tb, key) hash_blockno(key)
+#define SH_HASH_KEY(tb, key) murmurhash32(key)
#define SH_EQUAL(tb, a, b) a == b
#define SH_SCOPE static inline
#define SH_DEFINE
diff --git a/src/include/utils/hashutils.h b/src/include/utils/hashutils.h
index 56b7bfc9cb..35281689e8 100644
--- a/src/include/utils/hashutils.h
+++ b/src/include/utils/hashutils.h
@@ -20,4 +20,22 @@ hash_combine(uint32 a, uint32 b)
return a;
}
+
+/*
+ * Simple inline murmur hash implementation hashing a 32 bit ingeger, for
+ * performance.
+ */
+static inline uint32
+murmurhash32(uint32 data)
+{
+ uint32 h = data;
+
+ h ^= h >> 16;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+ return h;
+}
+
#endif /* HASHUTILS_H */
--
2.14.1.536.g6867272d5b.dirty
0005-Replace-binary-search-in-fmgr_isbuiltin-with-hashtv1.patchtext/x-diff; charset=us-asciiDownload
From f5c48bc7c0f2fcd3d1323c6e866baade9ab7212f Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 19:43:02 -0700
Subject: [PATCH 5/8] Replace binary search in fmgr_isbuiltin with hashtable.
Turns out we have enough functions that the binary search is quite
noticeable in profiles.
It'd be nice if there were a better place to build the hashtable than
the first pass through fmgr_isbuiltin() but there's currently none.
---
src/backend/utils/fmgr/fmgr.c | 63 ++++++++++++++++++++++++++++++++-----------
1 file changed, 47 insertions(+), 16 deletions(-)
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index a7b07827e0..87867f2183 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -26,6 +26,7 @@
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgrtab.h"
+#include "utils/hashutils.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -36,6 +37,30 @@
PGDLLIMPORT needs_fmgr_hook_type needs_fmgr_hook = NULL;
PGDLLIMPORT fmgr_hook_type fmgr_hook = NULL;
+/*
+ * Hashtable for fast lookup builtin functions.
+ */
+typedef struct BuiltinOidLookupEntry
+{
+ Oid foid;
+ int status;
+ const FmgrBuiltin *builtin;
+} BuiltinOidLookupEntry;
+
+/* define hashtable mapping block numbers to PagetableEntry's */
+#define SH_PREFIX oid2builtins
+#define SH_ELEMENT_TYPE BuiltinOidLookupEntry
+#define SH_KEY_TYPE Oid
+#define SH_KEY foid
+#define SH_HASH_KEY(tb, key) murmurhash32(key)
+#define SH_EQUAL(tb, a, b) a == b
+#define SH_SCOPE static inline
+#define SH_DEFINE
+#define SH_DECLARE
+#include "lib/simplehash.h"
+
+static oid2builtins_hash *oid2builtins = 0;
+
/*
* Hashtable for fast lookup of external C functions
*/
@@ -70,26 +95,32 @@ static Datum fmgr_security_definer(PG_FUNCTION_ARGS);
static const FmgrBuiltin *
fmgr_isbuiltin(Oid id)
{
- int low = 0;
- int high = fmgr_nbuiltins - 1;
+ BuiltinOidLookupEntry *entry;
- /*
- * Loop invariant: low is the first index that could contain target entry,
- * and high is the last index that could contain it.
- */
- while (low <= high)
+ /* TODO: it'd be better if this were done separately */
+ if (unlikely(oid2builtins == NULL))
{
- int i = (high + low) / 2;
- const FmgrBuiltin *ptr = &fmgr_builtins[i];
+ int i;
- if (id == ptr->foid)
- return ptr;
- else if (id > ptr->foid)
- low = i + 1;
- else
- high = i - 1;
+ oid2builtins = oid2builtins_create(TopMemoryContext,
+ fmgr_nbuiltins,
+ NULL);
+ for (i = 0; i < fmgr_nbuiltins; i++)
+ {
+ const FmgrBuiltin *ptr = &fmgr_builtins[i];
+ bool found;
+
+ entry = oid2builtins_insert(oid2builtins, ptr->foid, &found);
+ Assert(!found);
+ entry->builtin = ptr;
+ }
}
- return NULL;
+
+ entry = oid2builtins_lookup(oid2builtins, id);
+ if (entry)
+ return entry->builtin;
+ else
+ return NULL;
}
/*
--
2.14.1.536.g6867272d5b.dirty
0006-Add-pg_noinline-macro-to-c.hv1.patchtext/x-diff; charset=us-asciiDownload
From f7cce92a8542376f1ebeb952843ca2b87f0ceb1f Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 19:58:43 -0700
Subject: [PATCH 6/8] Add pg_noinline macro to c.h.
Forcing a function not to be inlined can be useful if it's the
slow-path of a performance critical function, or should be visible in
profiles to allow for proper cost attribution.
Author: Andres Freund
Discussion: https://postgr.es/m/
---
src/include/c.h | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/include/c.h b/src/include/c.h
index fd53010e24..f44610c0ef 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -642,6 +642,22 @@ typedef NameData *Name;
#define pg_attribute_noreturn()
#endif
+
+/*
+ * Forcing a function not to be inlined can be useful if it's the slow-path of
+ * a performance critical function, or should be visible in profiles to allow
+ * for proper cost attribution.
+ */
+/* GCC, Sunpro and XLC support noinline via __attribute */
+#if defined(__GNUC__) || defined(__SUNPRO_C) || defined(__IBMC__)
+#define pg_noinline __attribute__((noinline))
+/* msvc via declspec */
+#elif defined(_MSC_VER)
+#define pg_noinline __declspec(noinline)
+#else
+#define pg_noinline
+#endif
+
/* ----------------------------------------------------------------
* Section 6: assertions
* ----------------------------------------------------------------
--
2.14.1.536.g6867272d5b.dirty
0007-Improve-sys-catcache-performancev1.patchtext/x-diff; charset=us-asciiDownload
From 1dedcec8c68de02e9e0b360fe9c109218364b6e7 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 11 Sep 2017 18:25:39 -0700
Subject: [PATCH 7/8] Improve sys/catcache performance.
This primarily includes four pieces:
1) Avoidance of FunctionCallInfo based function calls, replaced by
more efficient functions with a native C argument interface.
2) Only initializing the ScanKey when necessary, i.e. catcache misses,
reduces cache unnecessary cpu cache misses.
3) Allowing the compiler to specialize critical SearchCatCache for a
specific number of attributes allows to unroll loops and avoid
other nkeys dependant initialization.
4) Split of the heap lookup from the hash lookup, reducing stack
allocations etc in the common case.
There's further potential:
- replace open coded hash with simplehash - the list walk right now
shows up in profiles.
- As oid is the only system column supported, avoid the use of
heap_getsysattr(), by adding an explicit branch for
ObjectIdAttributeNumber. This shows up in profiles.
- move cache initialization out of the search path
- add more proper functions, rather than macros for
SearchSysCacheCopyN etc., but right now they don't show up in profiles.
The reason the macro wrapper for syscache.c/h have to be changed,
rather than just catcache, is that doing otherwise would require
exposing the SysCache array to the outside. That might be a good idea
anyway, but it's for another day.
Author: Andres Freund
---
src/backend/utils/cache/catcache.c | 438 ++++++++++++++++++++++++++-----------
src/backend/utils/cache/syscache.c | 49 ++++-
src/include/utils/catcache.h | 18 +-
src/include/utils/syscache.h | 23 +-
4 files changed, 390 insertions(+), 138 deletions(-)
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index e092801025..67c596d29b 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -31,6 +31,7 @@
#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
+#include "utils/hashutils.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/rel.h"
@@ -72,11 +73,25 @@
/* Cache management header --- pointer is NULL until created */
static CatCacheHeader *CacheHdr = NULL;
+static inline HeapTuple SearchCatCacheInternal(CatCache *cache,
+ int nkeys,
+ Datum v1, Datum v2,
+ Datum v3, Datum v4);
+
+static pg_noinline HeapTuple SearchCatCacheMiss(CatCache *cache,
+ int nkeys,
+ uint32 hashValue,
+ Index hashIndex,
+ Datum v1, Datum v2,
+ Datum v3, Datum v4);
static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys,
- ScanKey cur_skey);
-static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache,
+ Datum v1, Datum v2, Datum v3, Datum v4);
+static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache, int nkeys,
HeapTuple tuple);
+static inline bool CatalogCacheCompareTuple(const CatCache *cache, int nkeys,
+ const HeapTuple tuple,
+ const Datum *arguments);
#ifdef CATCACHE_STATS
static void CatCachePrintStats(int code, Datum arg);
@@ -95,45 +110,127 @@ static HeapTuple build_dummy_tuple(CatCache *cache, int nkeys, ScanKey skeys);
*/
/*
- * Look up the hash and equality functions for system types that are used
- * as cache key fields.
- *
- * XXX this should be replaced by catalog lookups,
- * but that seems to pose considerable risk of circularity...
+ * Hash and equality functions for system types that are used as cache key
+ * fields. To compute hashes, and to check for hash collisions, use functions
+ * hardcoded for that purpose. This is sufficiently performance critical that
+ * the overhead of SQL style function calls is noticeable.
*/
+
+static bool
+chareqfast(Datum a, Datum b)
+{
+ return DatumGetChar(a) == DatumGetChar(b);
+}
+
+static uint32
+charhashfast(Datum datum)
+{
+ return murmurhash32((int32) DatumGetChar(datum));
+}
+
+static bool
+nameeqfast(Datum a, Datum b)
+{
+ char *ca = NameStr(*DatumGetName(a));
+ char *cb = NameStr(*DatumGetName(b));
+
+ return strncmp(ca, cb, NAMEDATALEN) == 0;
+}
+
+static uint32
+namehashfast(Datum datum)
+{
+ char *key = NameStr(*DatumGetName(datum));
+
+ return hash_any((unsigned char *) key, strlen(key));
+}
+
+static bool
+int2eqfast(Datum a, Datum b)
+{
+ return DatumGetInt16(a) == DatumGetInt16(b);
+}
+
+static uint32
+int2hashfast(Datum datum)
+{
+ return murmurhash32((int32) DatumGetInt16(datum));
+}
+
+static bool
+int4eqfast(Datum a, Datum b)
+{
+ return DatumGetInt32(a) == DatumGetInt32(b);
+}
+
+static uint32
+int4hashfast(Datum datum)
+{
+ return murmurhash32((int32) DatumGetInt32(datum));
+}
+
+static bool
+texteqfast(Datum a, Datum b)
+{
+ /* not as performance critical & "complicated" */
+ return DatumGetBool(DirectFunctionCall2(texteq, a, b));
+}
+
+static uint32
+texthashfast(Datum datum)
+{
+ /* not as performance critical & "complicated" */
+ return DatumGetInt32(DirectFunctionCall1(hashtext, datum));
+}
+
+static bool
+oidvectoreqfast(Datum a, Datum b)
+{
+ /* not as performance critical & "complicated" */
+ return DatumGetBool(DirectFunctionCall2(oidvectoreq, a, b));
+}
+
+static uint32
+oidvectorhashfast(Datum datum)
+{
+ /* not as performance critical & "complicated" */
+ return DatumGetInt32(DirectFunctionCall1(hashoidvector, datum));
+}
+
+/* Lookup support functions for a type. */
static void
-GetCCHashEqFuncs(Oid keytype, PGFunction *hashfunc, RegProcedure *eqfunc)
+GetCCHashEqFuncs(Oid keytype, CCHashFN *hashfunc, RegProcedure *eqfunc, CCFastEqualFN *fasteqfunc)
{
switch (keytype)
{
case BOOLOID:
- *hashfunc = hashchar;
-
+ *hashfunc = charhashfast;
+ *fasteqfunc = chareqfast;
*eqfunc = F_BOOLEQ;
break;
case CHAROID:
- *hashfunc = hashchar;
-
+ *hashfunc = charhashfast;
+ *fasteqfunc = chareqfast;
*eqfunc = F_CHAREQ;
break;
case NAMEOID:
- *hashfunc = hashname;
-
+ *hashfunc = namehashfast;
+ *fasteqfunc = nameeqfast;
*eqfunc = F_NAMEEQ;
break;
case INT2OID:
- *hashfunc = hashint2;
-
+ *hashfunc = int2hashfast;
+ *fasteqfunc = int2eqfast;
*eqfunc = F_INT2EQ;
break;
case INT4OID:
- *hashfunc = hashint4;
-
+ *hashfunc = int4hashfast;
+ *fasteqfunc = int4eqfast;
*eqfunc = F_INT4EQ;
break;
case TEXTOID:
- *hashfunc = hashtext;
-
+ *hashfunc = texthashfast;
+ *fasteqfunc = texteqfast;
*eqfunc = F_TEXTEQ;
break;
case OIDOID:
@@ -147,13 +244,13 @@ GetCCHashEqFuncs(Oid keytype, PGFunction *hashfunc, RegProcedure *eqfunc)
case REGDICTIONARYOID:
case REGROLEOID:
case REGNAMESPACEOID:
- *hashfunc = hashoid;
-
+ *hashfunc = int4hashfast;
+ *fasteqfunc = int4eqfast;
*eqfunc = F_OIDEQ;
break;
case OIDVECTOROID:
- *hashfunc = hashoidvector;
-
+ *hashfunc = oidvectorhashfast;
+ *fasteqfunc = oidvectoreqfast;
*eqfunc = F_OIDVECTOREQ;
break;
default:
@@ -171,10 +268,12 @@ GetCCHashEqFuncs(Oid keytype, PGFunction *hashfunc, RegProcedure *eqfunc)
* Compute the hash value associated with a given set of lookup keys
*/
static uint32
-CatalogCacheComputeHashValue(CatCache *cache, int nkeys, ScanKey cur_skey)
+CatalogCacheComputeHashValue(CatCache *cache, int nkeys,
+ Datum v1, Datum v2, Datum v3, Datum v4)
{
uint32 hashValue = 0;
uint32 oneHash;
+ CCHashFN *cc_hashfunc = cache->cc_hashfunc;
CACHE4_elog(DEBUG2, "CatalogCacheComputeHashValue %s %d %p",
cache->cc_relname,
@@ -184,30 +283,26 @@ CatalogCacheComputeHashValue(CatCache *cache, int nkeys, ScanKey cur_skey)
switch (nkeys)
{
case 4:
- oneHash =
- DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[3],
- cur_skey[3].sk_argument));
+ oneHash = (cc_hashfunc[3])(v4);
+
hashValue ^= oneHash << 24;
hashValue ^= oneHash >> 8;
/* FALLTHROUGH */
case 3:
- oneHash =
- DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[2],
- cur_skey[2].sk_argument));
+ oneHash = (cc_hashfunc[2])(v3);
+
hashValue ^= oneHash << 16;
hashValue ^= oneHash >> 16;
/* FALLTHROUGH */
case 2:
- oneHash =
- DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[1],
- cur_skey[1].sk_argument));
+ oneHash = (cc_hashfunc[1])(v2);
+
hashValue ^= oneHash << 8;
hashValue ^= oneHash >> 24;
/* FALLTHROUGH */
case 1:
- oneHash =
- DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[0],
- cur_skey[0].sk_argument));
+ oneHash = (cc_hashfunc[0])(v1);
+
hashValue ^= oneHash;
break;
default:
@@ -224,63 +319,96 @@ CatalogCacheComputeHashValue(CatCache *cache, int nkeys, ScanKey cur_skey)
* Compute the hash value associated with a given tuple to be cached
*/
static uint32
-CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple)
+CatalogCacheComputeTupleHashValue(CatCache *cache, int nkeys, HeapTuple tuple)
{
- ScanKeyData cur_skey[CATCACHE_MAXKEYS];
+ Datum v1 = 0, v2 = 0, v3 = 0, v4 = 0;
bool isNull = false;
-
- /* Copy pre-initialized overhead data for scankey */
- memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey));
+ int *cc_key = cache->cc_key;
+ TupleDesc cc_tupdesc = cache->cc_tupdesc;
/* Now extract key fields from tuple, insert into scankey */
- switch (cache->cc_nkeys)
+ switch (nkeys)
{
case 4:
- cur_skey[3].sk_argument =
- (cache->cc_key[3] == ObjectIdAttributeNumber)
+ v4 = (cc_key[3] == ObjectIdAttributeNumber)
? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
- cache->cc_key[3],
- cache->cc_tupdesc,
+ cc_key[3],
+ cc_tupdesc,
&isNull);
Assert(!isNull);
/* FALLTHROUGH */
case 3:
- cur_skey[2].sk_argument =
- (cache->cc_key[2] == ObjectIdAttributeNumber)
+ v3 = (cc_key[2] == ObjectIdAttributeNumber)
? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
- cache->cc_key[2],
- cache->cc_tupdesc,
+ cc_key[2],
+ cc_tupdesc,
&isNull);
Assert(!isNull);
/* FALLTHROUGH */
case 2:
- cur_skey[1].sk_argument =
- (cache->cc_key[1] == ObjectIdAttributeNumber)
+ v2 = (cc_key[1] == ObjectIdAttributeNumber)
? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
- cache->cc_key[1],
- cache->cc_tupdesc,
+ cc_key[1],
+ cc_tupdesc,
&isNull);
Assert(!isNull);
/* FALLTHROUGH */
case 1:
- cur_skey[0].sk_argument =
- (cache->cc_key[0] == ObjectIdAttributeNumber)
+ v1 = (cc_key[0] == ObjectIdAttributeNumber)
? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
- cache->cc_key[0],
- cache->cc_tupdesc,
+ cc_key[0],
+ cc_tupdesc,
&isNull);
Assert(!isNull);
break;
default:
- elog(FATAL, "wrong number of hash keys: %d", cache->cc_nkeys);
+ elog(FATAL, "wrong number of hash keys: %d", nkeys);
break;
}
- return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey);
+ return CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4);
+}
+
+/*
+ * CatalogCacheCompareTuple
+ *
+ * Compare a tuple to the passed arguments.
+ */
+static inline bool
+CatalogCacheCompareTuple(const CatCache *cache, int nkeys,
+ const HeapTuple tuple,
+ const Datum *arguments)
+{
+ TupleDesc tupdesc = cache->cc_tupdesc;
+ const int *cc_key = cache->cc_key;
+ const CCFastEqualFN *cc_fastequal = cache->cc_fastequal;
+ int i;
+
+ for (i = 0; i < nkeys; i++)
+ {
+ Datum atp;
+ bool isnull;
+
+ /*
+ * XXX: might be worthwhile to only handle oid sysattr, to reduce
+ * overhead - it's the most common key.
+ */
+ atp = heap_getattr(tuple,
+ cc_key[i],
+ tupdesc,
+ &isnull);
+ Assert(!isnull);
+
+ if (!(cc_fastequal[i])(atp, arguments[i]))
+ {
+ return false;
+ }
+ }
+ return true;
}
@@ -878,7 +1006,8 @@ CatalogCacheInitializeCache(CatCache *cache)
GetCCHashEqFuncs(keytype,
&cache->cc_hashfunc[i],
- &eqfunc);
+ &eqfunc,
+ &cache->cc_fastequal[i]);
cache->cc_isname[i] = (keytype == NAMEOID);
@@ -1020,7 +1149,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
}
/*
- * SearchCatCache
+ * SearchCatCacheInternal
*
* This call searches a system cache for a tuple, opening the relation
* if necessary (on the first access to a particular cache).
@@ -1042,15 +1171,64 @@ SearchCatCache(CatCache *cache,
Datum v3,
Datum v4)
{
- ScanKeyData cur_skey[CATCACHE_MAXKEYS];
+ return SearchCatCacheInternal(cache, cache->cc_nkeys, v1, v2, v3, v4);
+}
+
+
+/*
+ * SearchCatCacheN() are SearchCatCache() versions for a specific number of
+ * arguments. The compiler can inline the body and unroll the loop, making
+ * them a bit faster than SearchCatCache().
+ */
+
+HeapTuple
+SearchCatCache1(CatCache *cache,
+ Datum v1)
+{
+ return SearchCatCacheInternal(cache, 1, v1, 0, 0, 0);
+}
+
+
+HeapTuple
+SearchCatCache2(CatCache *cache,
+ Datum v1, Datum v2)
+{
+ return SearchCatCacheInternal(cache, 2, v1, v2, 0, 0);
+}
+
+
+HeapTuple
+SearchCatCache3(CatCache *cache,
+ Datum v1, Datum v2, Datum v3)
+{
+ return SearchCatCacheInternal(cache, 3, v1, v2, v3, 0);
+}
+
+
+HeapTuple
+SearchCatCache4(CatCache *cache,
+ Datum v1, Datum v2, Datum v3, Datum v4)
+{
+ return SearchCatCacheInternal(cache, 4, v1, v2, v3, v4);
+}
+
+/*
+ * Work-horse for SearchCatCache/SearchCatCacheN.
+ */
+static inline HeapTuple
+SearchCatCacheInternal(CatCache *cache,
+ int nkeys,
+ Datum v1,
+ Datum v2,
+ Datum v3,
+ Datum v4)
+{
+ Datum arguments[CATCACHE_MAXKEYS];
uint32 hashValue;
Index hashIndex;
dlist_iter iter;
dlist_head *bucket;
CatCTup *ct;
- Relation relation;
- SysScanDesc scandesc;
- HeapTuple ntp;
/* Make sure we're in an xact, even if this ends up being a cache hit */
Assert(IsTransactionState());
@@ -1058,26 +1236,23 @@ SearchCatCache(CatCache *cache,
/*
* one-time startup overhead for each cache
*/
- if (cache->cc_tupdesc == NULL)
+ if (unlikely(cache->cc_tupdesc == NULL))
CatalogCacheInitializeCache(cache);
#ifdef CATCACHE_STATS
cache->cc_searches++;
#endif
- /*
- * initialize the search key information
- */
- memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey));
- cur_skey[0].sk_argument = v1;
- cur_skey[1].sk_argument = v2;
- cur_skey[2].sk_argument = v3;
- cur_skey[3].sk_argument = v4;
+ /* Initialize local parameter array */
+ arguments[0] = v1;
+ arguments[1] = v2;
+ arguments[2] = v3;
+ arguments[3] = v4;
/*
* find the hash bucket in which to look for the tuple
*/
- hashValue = CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey);
+ hashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4);
hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets);
/*
@@ -1089,8 +1264,6 @@ SearchCatCache(CatCache *cache,
bucket = &cache->cc_bucket[hashIndex];
dlist_foreach(iter, bucket)
{
- bool res;
-
ct = dlist_container(CatCTup, cache_elem, iter.cur);
if (ct->dead)
@@ -1099,15 +1272,7 @@ SearchCatCache(CatCache *cache,
if (ct->hash_value != hashValue)
continue; /* quickly skip entry if wrong hash val */
- /*
- * see if the cached tuple matches our key.
- */
- HeapKeyTest(&ct->tuple,
- cache->cc_tupdesc,
- cache->cc_nkeys,
- cur_skey,
- res);
- if (!res)
+ if (!CatalogCacheCompareTuple(cache, nkeys, &ct->tuple, arguments))
continue;
/*
@@ -1150,6 +1315,42 @@ SearchCatCache(CatCache *cache,
}
}
+ return SearchCatCacheMiss(cache, nkeys, hashValue, hashIndex, v1, v2, v3, v4);
+}
+
+/*
+ * Search the actual catalogs, rather than the cache.
+ *
+ * This is kept separate from SearchCatCacheInternal() to keep the fast-path
+ * as small as possible. To avoid that effort being undone, try to explicitly
+ * forbid inlining.
+ */
+static pg_noinline HeapTuple
+SearchCatCacheMiss(CatCache *cache,
+ int nkeys,
+ uint32 hashValue,
+ Index hashIndex,
+ Datum v1,
+ Datum v2,
+ Datum v3,
+ Datum v4)
+{
+ ScanKeyData cur_skey[CATCACHE_MAXKEYS];
+ Relation relation;
+ SysScanDesc scandesc;
+ HeapTuple ntp;
+ CatCTup *ct;
+
+ /*
+ * Ok, need to make a lookup in the relation, copy the scankey and fill out
+ * any per-call fields.
+ */
+ memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * nkeys);
+ cur_skey[0].sk_argument = v1;
+ cur_skey[1].sk_argument = v2;
+ cur_skey[2].sk_argument = v3;
+ cur_skey[3].sk_argument = v4;
+
/*
* Tuple was not found in cache, so we have to try to retrieve it directly
* from the relation. If found, we will add it to the cache; if not
@@ -1171,7 +1372,7 @@ SearchCatCache(CatCache *cache,
cache->cc_indexoid,
IndexScanOK(cache, cur_skey),
NULL,
- cache->cc_nkeys,
+ nkeys,
cur_skey);
ct = NULL;
@@ -1207,7 +1408,7 @@ SearchCatCache(CatCache *cache,
if (IsBootstrapProcessingMode())
return NULL;
- ntp = build_dummy_tuple(cache, cache->cc_nkeys, cur_skey);
+ ntp = build_dummy_tuple(cache, nkeys, cur_skey);
ct = CatalogCacheCreateEntry(cache, ntp,
hashValue, hashIndex,
true);
@@ -1288,27 +1489,16 @@ GetCatCacheHashValue(CatCache *cache,
Datum v3,
Datum v4)
{
- ScanKeyData cur_skey[CATCACHE_MAXKEYS];
-
/*
* one-time startup overhead for each cache
*/
if (cache->cc_tupdesc == NULL)
CatalogCacheInitializeCache(cache);
- /*
- * initialize the search key information
- */
- memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey));
- cur_skey[0].sk_argument = v1;
- cur_skey[1].sk_argument = v2;
- cur_skey[2].sk_argument = v3;
- cur_skey[3].sk_argument = v4;
-
/*
* calculate the hash value
*/
- return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey);
+ return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, v1, v2, v3, v4);
}
@@ -1329,7 +1519,7 @@ SearchCatCacheList(CatCache *cache,
Datum v3,
Datum v4)
{
- ScanKeyData cur_skey[CATCACHE_MAXKEYS];
+ Datum arguments[CATCACHE_MAXKEYS];
uint32 lHashValue;
dlist_iter iter;
CatCList *cl;
@@ -1354,21 +1544,18 @@ SearchCatCacheList(CatCache *cache,
cache->cc_lsearches++;
#endif
- /*
- * initialize the search key information
- */
- memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey));
- cur_skey[0].sk_argument = v1;
- cur_skey[1].sk_argument = v2;
- cur_skey[2].sk_argument = v3;
- cur_skey[3].sk_argument = v4;
+ /* Initialize local parameter array */
+ arguments[0] = v1;
+ arguments[1] = v2;
+ arguments[2] = v3;
+ arguments[3] = v4;
/*
* compute a hash value of the given keys for faster search. We don't
* presently divide the CatCList items into buckets, but this still lets
* us skip non-matching items quickly most of the time.
*/
- lHashValue = CatalogCacheComputeHashValue(cache, nkeys, cur_skey);
+ lHashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4);
/*
* scan the items until we find a match or exhaust our list
@@ -1378,8 +1565,6 @@ SearchCatCacheList(CatCache *cache,
*/
dlist_foreach(iter, &cache->cc_lists)
{
- bool res;
-
cl = dlist_container(CatCList, cache_elem, iter.cur);
if (cl->dead)
@@ -1393,12 +1578,8 @@ SearchCatCacheList(CatCache *cache,
*/
if (cl->nkeys != nkeys)
continue;
- HeapKeyTest(&cl->tuple,
- cache->cc_tupdesc,
- nkeys,
- cur_skey,
- res);
- if (!res)
+
+ if (!CatalogCacheCompareTuple(cache, nkeys, &cl->tuple, arguments))
continue;
/*
@@ -1441,9 +1622,20 @@ SearchCatCacheList(CatCache *cache,
PG_TRY();
{
+ ScanKeyData cur_skey[CATCACHE_MAXKEYS];
Relation relation;
SysScanDesc scandesc;
+ /*
+ * Ok, need to make a lookup in the relation, copy the scankey and fill out
+ * any per-call fields.
+ */
+ memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * cache->cc_nkeys);
+ cur_skey[0].sk_argument = v1;
+ cur_skey[1].sk_argument = v2;
+ cur_skey[2].sk_argument = v3;
+ cur_skey[3].sk_argument = v4;
+
relation = heap_open(cache->cc_reloid, AccessShareLock);
scandesc = systable_beginscan(relation,
@@ -1467,7 +1659,7 @@ SearchCatCacheList(CatCache *cache,
* See if there's an entry for this tuple already.
*/
ct = NULL;
- hashValue = CatalogCacheComputeTupleHashValue(cache, ntp);
+ hashValue = CatalogCacheComputeTupleHashValue(cache, cache->cc_nkeys, ntp);
hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets);
bucket = &cache->cc_bucket[hashIndex];
@@ -1820,7 +2012,7 @@ PrepareToInvalidateCacheTuple(Relation relation,
if (ccp->cc_tupdesc == NULL)
CatalogCacheInitializeCache(ccp);
- hashvalue = CatalogCacheComputeTupleHashValue(ccp, tuple);
+ hashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, tuple);
dbid = ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId;
(*function) (ccp->id, hashvalue, dbid);
@@ -1829,7 +2021,7 @@ PrepareToInvalidateCacheTuple(Relation relation,
{
uint32 newhashvalue;
- newhashvalue = CatalogCacheComputeTupleHashValue(ccp, newtuple);
+ newhashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, newtuple);
if (newhashvalue != hashvalue)
(*function) (ccp->id, newhashvalue, dbid);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index fcbb683a99..888edbb325 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -1102,13 +1102,56 @@ SearchSysCache(int cacheId,
Datum key3,
Datum key4)
{
- if (cacheId < 0 || cacheId >= SysCacheSize ||
- !PointerIsValid(SysCache[cacheId]))
- elog(ERROR, "invalid cache ID: %d", cacheId);
+ Assert(cacheId >= 0 && cacheId < SysCacheSize &&
+ PointerIsValid(SysCache[cacheId]));
return SearchCatCache(SysCache[cacheId], key1, key2, key3, key4);
}
+HeapTuple
+SearchSysCache1(int cacheId,
+ Datum key1)
+{
+ Assert(cacheId >= 0 && cacheId < SysCacheSize &&
+ PointerIsValid(SysCache[cacheId]));
+ Assert(SysCache[cacheId]->cc_nkeys == 1);
+
+ return SearchCatCache1(SysCache[cacheId], key1);
+}
+
+HeapTuple
+SearchSysCache2(int cacheId,
+ Datum key1, Datum key2)
+{
+ Assert(cacheId >= 0 && cacheId < SysCacheSize &&
+ PointerIsValid(SysCache[cacheId]));
+ Assert(SysCache[cacheId]->cc_nkeys == 2);
+
+ return SearchCatCache2(SysCache[cacheId], key1, key2);
+}
+
+HeapTuple
+SearchSysCache3(int cacheId,
+ Datum key1, Datum key2, Datum key3)
+{
+ Assert(cacheId >= 0 && cacheId < SysCacheSize &&
+ PointerIsValid(SysCache[cacheId]));
+ Assert(SysCache[cacheId]->cc_nkeys == 3);
+
+ return SearchCatCache3(SysCache[cacheId], key1, key2, key3);
+}
+
+HeapTuple
+SearchSysCache4(int cacheId,
+ Datum key1, Datum key2, Datum key3, Datum key4)
+{
+ Assert(cacheId >= 0 && cacheId < SysCacheSize &&
+ PointerIsValid(SysCache[cacheId]));
+ Assert(SysCache[cacheId]->cc_nkeys == 4);
+
+ return SearchCatCache4(SysCache[cacheId], key1, key2, key3, key4);
+}
+
/*
* ReleaseSysCache
* Release previously grabbed reference count on a tuple
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index 200a3022e7..360f0c5dd5 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -34,6 +34,10 @@
#define CATCACHE_MAXKEYS 4
+
+typedef uint32 (*CCHashFN) (Datum datum);
+typedef bool (*CCFastEqualFN) (Datum a, Datum b);
+
typedef struct catcache
{
int id; /* cache identifier --- see syscache.h */
@@ -47,7 +51,8 @@ typedef struct catcache
int cc_nbuckets; /* # of hash buckets in this cache */
int cc_nkeys; /* # of keys (1..CATCACHE_MAXKEYS) */
int cc_key[CATCACHE_MAXKEYS]; /* AttrNumber of each key */
- PGFunction cc_hashfunc[CATCACHE_MAXKEYS]; /* hash function for each key */
+ CCHashFN cc_hashfunc[CATCACHE_MAXKEYS]; /* hash function for each key */
+ CCFastEqualFN cc_fastequal[CATCACHE_MAXKEYS]; /* fast equal function for each key */
ScanKeyData cc_skey[CATCACHE_MAXKEYS]; /* precomputed key info for heap
* scans */
bool cc_isname[CATCACHE_MAXKEYS]; /* flag "name" key columns */
@@ -174,8 +179,15 @@ extern CatCache *InitCatCache(int id, Oid reloid, Oid indexoid,
extern void InitCatCachePhase2(CatCache *cache, bool touch_index);
extern HeapTuple SearchCatCache(CatCache *cache,
- Datum v1, Datum v2,
- Datum v3, Datum v4);
+ Datum v1, Datum v2, Datum v3, Datum v4);
+extern HeapTuple SearchCatCache1(CatCache *cache,
+ Datum v1);
+extern HeapTuple SearchCatCache2(CatCache *cache,
+ Datum v1, Datum v2);
+extern HeapTuple SearchCatCache3(CatCache *cache,
+ Datum v1, Datum v2, Datum v3);
+extern HeapTuple SearchCatCache4(CatCache *cache,
+ Datum v1, Datum v2, Datum v3, Datum v4);
extern void ReleaseCatCache(HeapTuple tuple);
extern uint32 GetCatCacheHashValue(CatCache *cache,
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 8a92ea27ac..12bda02cd7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -117,6 +117,20 @@ extern void InitCatalogCachePhase2(void);
extern HeapTuple SearchSysCache(int cacheId,
Datum key1, Datum key2, Datum key3, Datum key4);
+
+/*
+ * The use of argument specific numbers is encouraged, they're faster, and
+ * insulates the caller from changes in the maximum number of keys.
+ */
+extern HeapTuple SearchSysCache1(int cacheId,
+ Datum key1);
+extern HeapTuple SearchSysCache2(int cacheId,
+ Datum key1, Datum key2);
+extern HeapTuple SearchSysCache3(int cacheId,
+ Datum key1, Datum key2, Datum key3);
+extern HeapTuple SearchSysCache4(int cacheId,
+ Datum key1, Datum key2, Datum key3, Datum key4);
+
extern void ReleaseSysCache(HeapTuple tuple);
/* convenience routines */
@@ -156,15 +170,6 @@ extern bool RelationSupportsSysCache(Oid relid);
* functions is encouraged, as it insulates the caller from changes in the
* maximum number of keys.
*/
-#define SearchSysCache1(cacheId, key1) \
- SearchSysCache(cacheId, key1, 0, 0, 0)
-#define SearchSysCache2(cacheId, key1, key2) \
- SearchSysCache(cacheId, key1, key2, 0, 0)
-#define SearchSysCache3(cacheId, key1, key2, key3) \
- SearchSysCache(cacheId, key1, key2, key3, 0)
-#define SearchSysCache4(cacheId, key1, key2, key3, key4) \
- SearchSysCache(cacheId, key1, key2, key3, key4)
-
#define SearchSysCacheCopy1(cacheId, key1) \
SearchSysCacheCopy(cacheId, key1, 0, 0, 0)
#define SearchSysCacheCopy2(cacheId, key1, key2) \
--
2.14.1.536.g6867272d5b.dirty
0008-WIP-Improve-getBaseTypeAndTypemod-performance-for-v1.patchtext/x-diff; charset=us-asciiDownload
From 597a829e0a42badc0eaba41597cf62681ba7ec62 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Wed, 13 Sep 2017 19:29:45 -0700
Subject: [PATCH 8/8] WIP: Improve getBaseTypeAndTypemod() performance for
builtin types.
Author: Robert Haas
Discussion: https://postgr.es/m/
---
src/backend/utils/cache/lsyscache.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index b7a14dc87e..2a22da1489 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2295,6 +2295,9 @@ getBaseTypeAndTypmod(Oid typid, int32 *typmod)
HeapTuple tup;
Form_pg_type typTup;
+ if (typid < FirstBootstrapObjectId)
+ break;
+
tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for type %u", typid);
--
2.14.1.536.g6867272d5b.dirty
On Sat, Sep 16, 2017 at 3:03 AM, Andres Freund <andres@anarazel.de> wrote:
Hi Thom,
On 2017-09-15 22:05:35 +0100, Thom Brown wrote:
Okay, I've retested it using a prepared statement executed 100,000
times (which selects a single row from a table with 101 columns, each
column is 42-43 characters long, and 2 rows in the table), and I get
the following:master:
real 1m23.485s
user 1m2.800s
sys 0m1.200spatched:
real 1m22.530s
user 1m2.860s
sys 0m1.140sNot sure why I'm not seeing the gain.
I suspect a part of that is that you're testing the patch in isolation,
whereas I tested it as part of multiple speedup patches. There's some
bigger bottlenecks than this one. I've attached my whole stack.But even that being said, here's the result for these patches in
isolation on my machine:
I have run the same test on Scylla for the single client. I have used
the same steps script as shared by you in above mail.
[mithun.cy@localhost ~]$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 56
On-line CPU(s) list: 0-55
Thread(s) per core: 2
Core(s) per socket: 14
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 63
Model name: Intel(R) Xeon(R) CPU E5-2695 v3 @ 2.30GHz
Stepping: 2
CPU MHz: 1200.761
BogoMIPS: 4594.35
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 35840K
NUMA node0 CPU(s): 0-13,28-41
NUMA node1 CPU(s): 14-27,42-55
(result is median of 3 pgbench runs, each run 5 mins)
Base TPS:
========
12199.237133
With Patches TPS:
===============
(0002-Add-more-efficient-functions-to-pqformat-API.patch +
0003-Improve-performance-of-SendRowDescriptionMessage.patch)
13003.198407 (an improvement of 6.5%)
Perf report also says same.
Base: perf_bmany_cols
-------------------------------
19.94% 1.86% 11087 postgres postgres [.]
SendRowDescriptionMessage
|
---SendRowDescriptionMessage
|
|--99.91%-- exec_describe_portal_message
| PostgresMain
| ExitPostmaster
| BackendStartup
| ServerLoop
| PostmasterMain
| startup_hacks
| __libc_start_main
--0.09%-- [...]
After Patch: perf_many_cols
--------------------------------------
16.80% 0.04% 158 postgres postgres [.]
SendRowDescriptionMessage
|
---SendRowDescriptionMessage
|
|--99.89%-- exec_describe_portal_message
| PostgresMain
| ExitPostmaster
| BackendStartup
| ServerLoop
| PostmasterMain
| startup_hacks
| __libc_start_main
--0.11%-- [...]
So I think performance gain is visible. We saved a good amount of
execution cycle in SendRowDescriptionMessagewhen(my callgrind report
confirmed same) when we project a large number of columns in the query
with these new patches.
--
Thanks and Regards
Mithun C Y
EnterpriseDB: http://www.enterprisedb.com
Attachments:
perf_report.tar.gzapplication/x-gzip; name=perf_report.tar.gzDownload
� �|�Y ��ks�H�(z>��`����'�����V��;3�����g�Q%�c���uc~�E@�
�U�-Y�1�FS� ��We�c����fU���fY��� �u�0�5�zI����A�~�&3/��$��(�f����?f��<� vU]������n=�n��������>����.���x��l�m��-�b���[V������m����l����E���wq�Y/g�m���+������}v]������,����n�>�u=����y���lV��7����?�v[n�:�����,��0uC/K������d���k���u��-f������[��CQ����7�d�z��qu�Y������`���}>����������I<�����z��f�MU����.7���������c���/9��:_���w���_�����8��o�<^�?�������c�������>���p���|�*�������O��3|�����Y����l���5���t�����O������������>���M�~]���2�M�t����~1���y����i ���)� �{:��)�f9Y��5�P�5�0� �&�����b}��XU�l6�_�����k����E��/rq1�����I��JZ8���E�7(-��Li��{�m~�F�$���G�����w?u2��wbY����w��v5��z�{�����^������>r��W�0Z��R�,�;`J������
c�|/�� ��`��=����E��^�H�0o�i��H$:x��*�b:b�g2c< �<(�% s� &|�J e] �\+�����=/1�b��w}[���$;O�}tL��������:�aw��I?��_����,&vF�|���W��WE�
I���IFp*�{��l� ��5�������
��k�t��!x
\$@�$@ �NLK�d������ ��g�_�����\cc`t�c(E@A$a����� [��i�.������# �4t�B4� Ei����pG�#�����-�G�* �GK���c�F�e�Hg`��������{�Y� �M�X��j8�=&�dEb���gf7_�n#q9�D��Z�|����]&���;�O�,���;*��gU3� �Q�@;�]H'&_�����5t�(�3q*���4��YQ����b��
u���O��A�������*�S���� &���e�����_~Y7/c],��T��~}����#A��`�wFL�d�Y�L�3�������a������\�k����m�����I~�2�d�~t�7�����p�OE�8��Y�6��6��/����|�&�\���N��"o�
�������|���&���e=�|(=lt���hCI���7��w�j�|l��f��z�t�n}��'�i�X�r��?��4�+s��yG�Y2���������eQUy1_6��q��^>����o��r5��������?2�M�E�t��Aw{��T��s7�����-���� �AO$(��7n�t~�yn
������Oi����'y�
?�i�������abb{�E}�����W"��
�bJ|���l��&�hL��+z�x'���8-��Md�P����J���e�2�i��s�E<���p}�9��
�ZDGq�Rt��<|��_0<��B�-G�Z�����b��`]/���1����uB� �����������-qR�����V�#�C��w�|^���O�gD����%�a'�{�r���T\�}�^���
�ER3#�yR���PJ=,m�#��
�
:�~j���/ow
��`�9������3�M^U�{x6����������T��gs�����r����a���:�&�!��P�!�w�:9��+�c��u0-�6R������I�_�([�x����$_�)�8�D9=-U="F���#"}(����}
����a�c#������f������K�#����#���)T<�!I��(��=� ��*��%���dO��7��^,P Lj���SsH5�d?�@���� ����nM���{�-f���F��Y���[l��{�y��5�0/�����VZ� �OI'za�$1u{�Z��W�3j����hQH�3���F��������F������r�8�V�d:h�<�*�z�I6�����x~9 ��@��l m��#x�����^��}4��[���:�>�!P>�^��"��S���4j�r��|N{���w����=vc��DU0�u�"��2���-r�~2���\6���'
�\��F* �<���~���wi����C�p tp���No=�-�8AL'R�Q�K��KQ@��k��X��4D]l!tb�T�O� ��z�it�T�a2�pJ������8���"��b��H�E����E�$�6zLJ���H}p��r3?���=����b�L��-�f1�r��d^q`2=���4���$������[���i;@�H|H|kH���JC����,�F''#G���R==8������>�A|���X���r�Bh_J�Ya�K0-F�S��4 ��������)y��BhI5Z���2R����1<�`L�e�9_�K����[!���B�-���3�^�
;�w��' V�n���p���� Z�����.���t��T��Y���kA��`���|�����P[��7y����B�g ��5x.�@�GyJE*��J�c����cO��b�����t��������|P��|d��-���rY=��?f��?f�c�-V���8�k������nA�x����,�O�����:K�BLn����Q��K������������J�u�7�i��`\�������m��C/��{�~�n<��Q��~�~��l�i��K����f^��LW��{���g���Y��uZ����a�����l��fw�_t���G�H\���A��s��f�*��H���UyQ��Z���X����HUx�s���IG��
h�����s����������_�!������t|�_�Z_�
y�$_��(`��V����{XG�c~�!W������e��@mK�D��S��{���lnds��N�\�D`��B���}f6��s@�x��=N���Q�������x�S������o���X��4��|�)������A�xO59��Mi:2�����Yf������Tq�$'y��TnM;3�'�K�4�� ����N��G����Q-�7x�������g��dMD��0�����x����tB����x�<=�����S��EF^'����&�#���/7�l���x��1�g��}�>������E�6��@�9,Z��q7�V!3���S��$�f����z$K�-H?��O���I�~i�d�<��:?V~��Kd�Y8J�>���d�I�p��9I ��t�GDQ����;��6�5�2���d�(�H��d����N �*�$���&<"� �H�� LxD�����]��5���,��" �!��
�'�����~Dm�1� (�m� �
e��|����*#�x3v�yz�,�{j�"]���X:�����IL��h5�$O��=��������lV�+�=�J@��A�D�� �6��-�~.��w��u�g*� � �Vb��������l�| �� �<������FHKF����{N�$����m�
o�_��F�Y�����]����}��{8�F)����]�=T�I�������@T�gJ����W�r��`r��!�� �k=>�bP_�Sq��v����z���E�o�E-kc���U��]��zOtR_����0Y�
�hm�S��OP���E� 4�; c���r� \���'MpU����*rI�����FT�{�W����3�����]�Z,�����^0T�-U�J�F^���;���V��N�>M/�8vR�\)r�H��4�4p��B\`42�$�/O��n�'��&p������Z�������@0��J:i����8���������})����N��D''�:��KtY���`{ � 7�Up���S
����]����[�F���"E��X��gJ�]g�F�&��@��N��X.�?����%R�b��2nx����k G���+Y��N��'v&v>+;�=��<-Ib��M�j��F}��/�IYp��3��S������F%�S��/�Y�����0��`�������$F��G�P`tG�������,�������8�������X�����m�a���T�UQ�!"���z�BF���� x��dG��\F �1F���R�g����g�1�T��d/c�T:7�\7Bd���u^5��\����s����Z��]0�T�sk�PfrV&]RV��^���=����5=������7�D�$��,T� =�8���W��-z�����CHG�
j�G
�^bO��p��J�_C���xWHn�-q5${a������A2� r�L��h������{�ep*�Q�oI��b��YKE#���g��5��J�/�4
Q����������*��E��-�u>o�����D���Z������ �F�s����
{�sq�*���E��,�U��?�^�������~"�11� �����#�����K�� Hf1TLG��Z(p�h��<c��o�z1f}PI��������~�n�%������a�b�a�?�~)������1���W�>�������._�,��o(@o�e:8��N�N����z��]��:��b����.k~�:/o��s���>l��}����_�}��
��{>�N�2����Ne|w�0i�����\RO������A\���@��7�=�����S�{<G:��E��,&B�&��8�K��(�A�k���T`(�YN��33�a�0��>��J�F|?�@���d���-��B���>��o+��h�.8���3�)*8#��:L��8�*8{����l��3����3����E)�q���m��h��4� �z�~>r
ju����D��7Z�.�-&���3�>�\\��R/Am���:������N@��t����M��_Q�9
�X�T�`�B"]'�^�������3�mWa�!^$;j),��z��.u��������A��D�#0g�8c �3���{��#�>(1����w���+���0����9����w�p�od���A%�8�[����-���2�+oK��lC�1��
/��8ta^���i���F��9�S��xH���
�8s�&ww�y��n����O? ��)��?V� ���,�R.��D� �.�Ao;�H�U���.L�)U+��%H�{ �_��� ��K<��K�@�$8}
y�7��@�x���In��y�P�@�����?��)���La��z}P�����l��fw7��&����g�����c��g�L*BK�g�j8���{g�
�m�P6�=���8��;�n<X�<:�A2Y���rL����8&��B�n?|��m-���62���j�H����a�T�*a�"62\��8 �F��A���k�l����l��-��qD�I<s��� �c��Dd�P��[~)b7X:2�-p��P�$�$)�|P���E���,f�r�\�>���:h[���,�b���,������f�QT/�;����r�� �������B��� ����N~fU�x���������]�Y}��/��2�����N�X�[�&���L��?������B������]�������%UY���� ���~'h?���f}���� �������|���\l�S�W��7,�
�#Nzy��v��F�7��w�j�|l4�f��z�P�WT�%=�J$w#���a,yJ�V�(��~����%�
k�">�9�-z:!y��[��N
���?t�& ����w��<"MqP(9�$�/N�}'I(:�!F��%&Eg����_����r�{���U�+�7H�F�F��5��� ;��o_x��^%��^��m�^�=�����~dNo�':I�9����e����b�m��O�uQ>����T�w���O"�w�����%N�v���L���Z���X[J���[���
���h��-�|���>�@�#�c����O��(���Gc���{����&�)��lJ��M LdS���iM&]O���T���p�`b6$0�P[�o;o*v���9��y����d�8?����"����4���x�5�vs0�&�D/*C*��0��.hv�cM��^��?>�r����b��W�3�������et��h� C������@�V��y����i�E����$�M�al=�PY���l+_�.����Dt�MJc}�;)�o��X|+��� ����__�V/���&��N ��b�T�@�>��;��8p����*�j���b����]%�K��N��� }t�:e�'f��9��l�3�g��u��J�%9��k2��N�d[�NIz�g�B�:l� ^D]��[k��u���������\{&spI�jH��U2����%m0���e���`�B���]���|�,�*/����>Nh��6�x���{V�tO'��ZJN����m������I\�t�M\���f�Q��W����}��d:8 :t��E�)�>��H�����d���X��������������XM� ��+��u@z�� ��Uu�V/|���S��D�J2��m���w'P�3��J����l�]��<l������ F��A��}.N\�]'��z?��'�d���7J���lF}�8�����J���������D@z����i5�|�I���4���*.J�G�Qtl?�t�� ���4Sn��;����@��X��N�_�H����y J\5���( �����+��}x�!��G:�|��qw��:K�����M�h��+
��G>�q���y� F�zhP�A�x9�D�'��Z�+P0�xt��}��s����d4
:A�S�����4�@'����0z �� �v ��`0Z�Jh�HN���l� :d3����8��~(���bj�5���AzPa�i��������X8\����n����.�"i�f� !c�n��a�e�S�;~��-�DD�
��:���0|n�Z:������h�1�9��~�����r�K�4CC���������n���FAmIG��g}f��~p��>�$~j���d��?��+c��8���/���EN�Xl����]�Ib�T �Q��@<�����X�T�0���8�uR�TP���I�������������%���'��&��jh��"����J��B�w!y{Yx�u[^/7uoAzOt�pB�Hc����A�&x_n��D���A9��
P��������^�kS� 4���R�d���
��|'
�2����d� ����4rK�������]�]Q�*\$?�H����j,iT��wa#Mz3������E���S�Z�8QJ�d��6l���M�����~l �?��[��{�t�?��[��� ���m�\+�)h;����I�����U�)�Y�x�����Im8��2��y�����.�&��4����`���_�s�hh��v~��}�I2O�Dp{��K��%�������t�����p���?`w�0���{�K��"�g �����L|��K��#���)I�����tcSS�}��#t�`��������H�3i# �F�FI`��z�d��$��FXj# ,����H;m$��J�����Id��K`�iK`�7��qE�d[�i�7����S4��f�
l�X*=1��R/�Y*�K�^p6BhK�^`h��� k��L,�z���R/�Z*�K�^``��L��%�q�6�~*k��E����B�4 ��L��vc��T�M�:��@��� 4�h�;x6$
L�J������tc�� ��`}'H�
� H�I �V�N��:���
�����!Y;'�����cH��(S��T+��_&f?$.�F\�K�$�|* ��U'������#z���!�y�Vo����2�����z���)��A��k�5���L3��o$}zq�&]sx��Qj�����#<U#bn^_�?�9o6Zl����������;��3Q~�OM<A�JTj�(J����K��0��(��RD�C
���� o p�-��xK`��o ��ww�xK`"����X���D�%0o �#�cE%P7��;�M��(�T���}��|�{OtR]pB��M]��j |�r�n��E���3�i���\M���S?s$:������Pj|D����9������'��\sI,��Y���)��x;qH�����!��<c:�����z4�
;�u���G d���<���~�,Q3� �=��g+��iK�'H�w�h=��p���K��j��pr����g=�����
lsg��`���,�Sd���L� ��8��� �� ��%0��c��H-.��W`!��~j�
F�(-.��?y 3\#-��Gc�k���-��-��-+�-��-k��
��$�2q���`�i~���0U���OY @f@��C��@��P��@��6���j�48���� ��On�@��`l9R��nD�8��'��/���#M���Pm�$'�6�4=nF��8��M��x M��Q�! �srq���+���^���>.�+��w���`G�O
#�aRdl� ����>
��� '�O�>=C�
�v"��NL�����(�w����e��q��6��Q������������S�:njpXNN9y�����}�<���>\+r-1�%���m���>���<'