SendRowDescriptionMessage() is slow for queries with a lot of columns
Andres Freund <andres@anarazel.de>
From: Andres Freund <andres@anarazel.de>
To: pgsql-hackers@postgresql.org
Date: 2017-09-14T06:34:18Z
Lists: pgsql-hackers
Attachments
- 0002-Add-more-efficient-functions-to-pqformat-API.patch (text/x-diff)
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? 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
Commits
-
Replace remaining uses of pq_sendint with pq_sendint{8,16,32}.
- 31079a4a8e66 11.0 landed
-
Improve performance of SendRowDescriptionMessage.
- 4c119fbcd49b 11.0 landed
-
Use one stringbuffer for all rows printed in printtup.c.
- f2dec34e19d3 11.0 landed
-
Add configure infrastructure to detect support for C99's restrict.
- 0b974dba2d6b 11.0 landed
-
Add more efficient functions to pqformat API.
- 1de09ad8eb1f 11.0 landed
-
Allow to avoid NUL-byte management for stringinfos and use in format.c.
- 70c2d1be2b1e 11.0 landed
-
Replace most usages of ntoh[ls] and hton[sl] with pg_bswap.h.
- 0ba99c84e8c7 11.0 landed
-
Extend & revamp pg_bswap.h infrastructure.
- 510b8cbff15f 11.0 landed