| From: | Sehrope Sarkuni <sehrope(at)jackdb(dot)com> |
|---|---|
| To: | Pg Hackers <pgsql-hackers(at)postgresql(dot)org> |
| Subject: | Speed up lpad() and rpad() for one-byte padding strings |
| Date: | 2026-09-23 14:10:09 |
| Message-ID: | CAH7T-apj+pFg9bRkXVGfGejS2Uu4WzdMd7KoLKTW11mdsrt1Ew@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi hackers,
lpad() and rpad() pad one character at a time, calling
pg_mblen_range() and memcpy() once per padding char. When the padding
string is a single byte, e.g., lpad(x, n, '0') or rpad(x, n, ' '),
the padding is that byte repeated, so the attached patch fills it with
one memset().
pg_mblen_range() is still called once on the byte, so a lone lead byte
of a multibyte character is rejected as before. The fast path is
skipped when no padding is needed, so such a byte is still accepted in
that case. Multibyte and multi-character padding strings still use
the loop.
One oddity of the current implementation I noticed is that the pad
string is only validated when it is being copied. So an invalid pad
string is accepted when no padding is needed. This patch preserves
that behavior but it seemed weird enough (to me) to mention here.
The existing strings.sql tests already run the one-byte path through
the default space padding. The patch adds tests to encoding.sql,
for padding with a multibyte character and for the lone-lead-byte
cases.
Timings on an AMD Ryzen 7 5700G, release build (-O3, no asserts),
pgbench -c 1 with one statement per transaction, alternating
before/after rounds, median latency in ms of 5 rounds (3 at 100M):
SELECT octet_length(rpad('x', N, ' '))
or
SELECT octet_length(lpad('x', N, '0'))
N rpad before after lpad before after
1 0.056 0.056 0.057 0.056
10 0.056 0.056 0.057 0.056
100 0.056 0.056 0.057 0.057
1000 0.062 0.056 0.062 0.056
10000 0.106 0.058 0.106 0.057
100000 0.543 0.061 0.548 0.061
1000000 4.799 0.102 4.805 0.102
10000000 53.415 5.350 53.773 5.194
100000000 567.672 96.637 567.826 96.564
controls (generic loop):
octet_length(rpad('x', 1000000, 'é')) 5.342 5.676
octet_length(lpad('x', 1000000, 'ab')) 4.841 5.078
The generic-loop controls are consistently about 5-6% slower. I
tried a few arrangements of the if-block, but they produced the same
code layout. The generic loop now straddles a 64-byte boundary where
it fit within one before. I wasn't able to eliminate that difference
by rearranging the fast path.
Large pads with a single byte seem far more common than large pads
with a multi-byte or multi-character string.
Passes check-world with asserts enabled.
Regards,
-- Sehrope Sarkuni
Founder & CEO | JackDB, Inc. | https://www.jackdb.com/
| Attachment | Content-Type | Size |
|---|---|---|
| 0001-Use-memset-for-one-byte-padding-in-lpad-and-rpad.patch | text/x-patch | 4.9 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Manu | 2026-09-23 14:16:53 | Re: REPACK (CONCURRENTLY) can silently lose updates when the toast table is rewritten |
| Previous Message | Ayush Tiwari | 2026-09-23 13:20:14 | Re: [PATCH] Two remaining shmem attachment issues in single-user mode |