pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedOptimize UUID parse using SIMD
27+ messages / 5 participants
[nested] [flat]
* Optimize UUID parse using SIMD
@ 2026-06-25 18:27 Masahiko Sawada <sawada.mshk@gmail.com>
0 siblings, 2 replies; 27+ messages in thread
From: Masahiko Sawada @ 2026-06-25 18:27 UTC (permalink / raw)
To: pgsql-hackers
Hi all,
I'd like to propose the $subject.
Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
a run of hex digits is now fast. The attached patch reuses
hex_decode_safe() in the UUID input function to speed up parsing.
We accept several textual forms of a UUID[1]. The fast path handles
the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
(where "nx" means n hex digits), and either of those wrapped in
braces. Otherwise, it falls back to the ordinary scalar UUID parse.
I've benchmarked the parse speed using the following query:
CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
generate_series(1, 1000000);
EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
I compared the execution time of the second query, which measures
uuid_in() alone, with/without SIMD optimization. Here are results (the
median of 5 runs):
HEAD: 208.879 ms
Patched: 40.983 ms
The improvements look promising to me. But in a realistic pipeline the
parse is a small fraction of the work, so end-to-end gains could be
much smaller.
Feedback is very welcome.
Regards,
[1] https://www.postgresql.org/docs/devel/datatype-uuid.html#DATATYPE-UUID
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-patch] v1-0001-Optimize-UUID-parse-using-SIMD.patch (3.8K, ../../CAD21AoCqeR4UQU77Q_yOMNNzJ7AVeiO5QZT+4HnzPm4Wm-e02Q@mail.gmail.com/2-v1-0001-Optimize-UUID-parse-using-SIMD.patch)
download | inline diff:
From 72e59ea260aaf845cd856cbdf36a2502694c298a Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 25 Jun 2026 10:03:44 -0700
Subject: [PATCH v1] Optimize UUID parse using SIMD.
Author:
Reviewed-by:
Discussion: https://postgr.es/m/
---
src/backend/utils/adt/uuid.c | 92 ++++++++++++++++++++++++++++++++++--
1 file changed, 87 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 6ee3752ac78..08272527669 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -19,7 +19,9 @@
#include "common/hashfn.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
#include "port/pg_bswap.h"
+#include "utils/builtins.h"
#include "utils/fmgrprotos.h"
#include "utils/guc.h"
#include "utils/skipsupport.h"
@@ -122,13 +124,10 @@ uuid_out(PG_FUNCTION_ARGS)
}
/*
- * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
- * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
- * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
- * digits, is the only one used for output.)
+ * General UUID parser.
*/
static void
-string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+string_to_uuid_scalar(const char *source, pg_uuid_t *uuid, Node *escontext)
{
const char *src = source;
bool braces = false;
@@ -177,6 +176,89 @@ syntax_error:
"uuid", source)));
}
+/*
+ * SIMD fast path. Handles the four common shapes directly and delegates everything
+ * else to string_to_uuid_scalar().
+ */
+#ifndef USE_NO_SIMD
+static void
+string_to_uuid_simd(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+ const char *body = source;
+ size_t len = strlen(source);
+ const char *hexsrc = NULL;
+ char hexbuf[32];
+ ErrorSaveContext esctx = {T_ErrorSaveContext};
+
+ /* Strip one optional surrounding brace pair */
+ if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
+ {
+ body = source + 1;
+ len -= 2;
+ }
+
+ if (len == 32)
+ {
+ /*
+ * Body is already 32 contiguous hex digits -- decode straight from
+ * the input. hex_decode_safe() reads exactly body[0..31], so it never
+ * touches the trailing NULL or '}'.
+ */
+ hexsrc = body;
+ }
+ else if (len == 36 &&
+ body[8] == '-' && body[13] == '-' && body[18] == '-' &&
+ body[23] == '-')
+ {
+ /*
+ * The canonical format 8x-4x-4x-4x-12x format. Compact them into
+ * hexbuf with fixed-offset copies.
+ */
+ memcpy(&hexbuf[0], &body[0], 8);
+ memcpy(&hexbuf[8], &body[9], 4);
+ memcpy(&hexbuf[12], &body[14], 4);
+ memcpy(&hexbuf[16], &body[19], 4);
+ memcpy(&hexbuf[20], &body[24], 12);
+ hexsrc = hexbuf;
+ }
+
+ if (hexsrc == NULL)
+ {
+ /* Uncommon shape; let the general parse handle it */
+ string_to_uuid_scalar(source, uuid, escontext);
+ return;
+ }
+
+ /*
+ * Decode the UUID hex data using our hex decoder that is SIMD-aware. We
+ * pass the local esctx instead of escontext to hex_decode_safe() to
+ * shallow any raised by hex_decode_safe(), then fall back to the general
+ * UUID parser for the correct error.
+ */
+ (void) hex_decode_safe(hexsrc, 32, (char *) uuid->data, (Node *) &esctx);
+
+ if (esctx.error_occurred)
+ string_to_uuid_scalar(source, uuid, escontext);
+}
+#endif
+
+/*
+ * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
+ * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
+ * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
+ * digits, is the only one used for output.)
+ */
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+#ifdef USE_NO_SIMD
+ string_to_uuid_scalar(source, uuid, escontext);
+#else
+ string_to_uuid_simd(source, uuid, escontext);
+#endif
+}
+
+
Datum
uuid_recv(PG_FUNCTION_ARGS)
{
--
2.54.0
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-25 19:30 Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
1 sibling, 1 reply; 27+ messages in thread
From: Bharath Rupireddy @ 2026-06-25 19:30 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: pgsql-hackers
Hi,
On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> a run of hex digits is now fast. The attached patch reuses
> hex_decode_safe() in the UUID input function to speed up parsing.
>
> We accept several textual forms of a UUID[1]. The fast path handles
> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> (where "nx" means n hex digits), and either of those wrapped in
> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
>
> I've benchmarked the parse speed using the following query:
>
> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> generate_series(1, 1000000);
> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
>
> I compared the execution time of the second query, which measures
> uuid_in() alone, with/without SIMD optimization. Here are results (the
> median of 5 runs):
>
> HEAD: 208.879 ms
> Patched: 40.983 ms
Nice!
> The improvements look promising to me. But in a realistic pipeline the
> parse is a small fraction of the work, so end-to-end gains could be
> much smaller.
>
> Feedback is very welcome.
I had a quick look at the patch. It mostly looks good to me. I like
the idea of falling back to the scalar path when an error occurs - a
neat user experience.
I think it's not worth adding a test case for this because I believe
this code gets covered anyway from existing tests.
A few comments:
1/
+ * pass the local esctx instead of escontext to hex_decode_safe() to
Instead of using variable names in the comments, let's say something
like: we pass a separate error context to detect errors in the SIMD
path and fall back to the normal path instead of raising ERRORs, for a
better user experience.
2/
+ if (esctx.error_occurred)
+ string_to_uuid_scalar(source, uuid, escontext);
An error on supported platforms seems rare, but when one occurs, I
think it's worth emitting a WARNING or LOG message. This way the query
succeeds, but later in the server logs, if noticed, it could provide
useful reasoning or uncover issues in the SIMD code.
--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-25 21:31 Haibo Yan <tristan.yim@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
1 sibling, 1 reply; 27+ messages in thread
From: Haibo Yan @ 2026-06-25 21:31 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: pgsql-hackers
On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com>
wrote:
> Hi all,
>
> I'd like to propose the $subject.
>
> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> a run of hex digits is now fast. The attached patch reuses
> hex_decode_safe() in the UUID input function to speed up parsing.
>
> We accept several textual forms of a UUID[1]. The fast path handles
> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> (where "nx" means n hex digits), and either of those wrapped in
> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
>
> I've benchmarked the parse speed using the following query:
>
> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> generate_series(1, 1000000);
> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
>
> I compared the execution time of the second query, which measures
> uuid_in() alone, with/without SIMD optimization. Here are results (the
> median of 5 runs):
>
> HEAD: 208.879 ms
> Patched: 40.983 ms
>
> The improvements look promising to me. But in a realistic pipeline the
> parse is a small fraction of the work, so end-to-end gains could be
> much smaller.
>
> Feedback is very welcome.
>
> I may be missing something, but I wonder whether the fast path is relying
on
slightly different input semantics from the existing UUID parser.
In particular, hex_decode_safe() is not a strict “32 hex characters only”
decoder. It skips whitespace, which is fine for its existing callers, but I
don’t think UUID input should treat whitespace inside the UUID body as
ignorable. Also, since hex_decode_safe() returns void, the UUID fast path
cannot verify that exactly UUID_LEN bytes were produced.
So I think it would be safer either to pre-validate that the 32 source
characters are all hex digits before calling hex_decode_safe(), or to use a
UUID-specific strict hex decoder for this path. After that, a comment
explaining why hex_decode_safe() is safe here would make the invariant much
clearer.
Could you also add a few regression tests for invalid inputs that contain
whitespace inside otherwise fast-path-looking UUID strings? For example:
---------------------------------------------------------------
SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
---------------------------------------------------------------
These should continue to be rejected in the same way as the scalar parser.
Regards,
Haibo
> Regards,
>
> [1] https://www.postgresql.org/docs/devel/datatype-uuid.html#DATATYPE-UUID
>
> --
> Masahiko Sawada
> Amazon Web Services: https://aws.amazon.com
>
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-25 22:16 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Haibo Yan <tristan.yim@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Masahiko Sawada @ 2026-06-25 22:16 UTC (permalink / raw)
To: Haibo Yan <tristan.yim@gmail.com>; +Cc: pgsql-hackers
On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>
>
>
> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>
>> Hi all,
>>
>> I'd like to propose the $subject.
>>
>> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
>> a run of hex digits is now fast. The attached patch reuses
>> hex_decode_safe() in the UUID input function to speed up parsing.
>>
>> We accept several textual forms of a UUID[1]. The fast path handles
>> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
>> (where "nx" means n hex digits), and either of those wrapped in
>> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
>>
>> I've benchmarked the parse speed using the following query:
>>
>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
>> generate_series(1, 1000000);
>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
>>
>> I compared the execution time of the second query, which measures
>> uuid_in() alone, with/without SIMD optimization. Here are results (the
>> median of 5 runs):
>>
>> HEAD: 208.879 ms
>> Patched: 40.983 ms
>>
>> The improvements look promising to me. But in a realistic pipeline the
>> parse is a small fraction of the work, so end-to-end gains could be
>> much smaller.
>>
>> Feedback is very welcome.
>>
> I may be missing something, but I wonder whether the fast path is relying on
> slightly different input semantics from the existing UUID parser.
>
> In particular, hex_decode_safe() is not a strict “32 hex characters only”
> decoder. It skips whitespace, which is fine for its existing callers, but I
> don’t think UUID input should treat whitespace inside the UUID body as
> ignorable.
Good catch! hex_decode_safe() skips whitespaces so the patch accepts
the following UUID value, which is bad:
select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
uuid
--------------------------------------
019f00b5-7f8a-722f-b707-59f0ed25cd00
(1 row)
> Also, since hex_decode_safe() returns void, the UUID fast path
> cannot verify that exactly UUID_LEN bytes were produced.
IIUC hex_decode_safe() does return the output length in bytes. So I
think we can fallback to the scalar UUID parser if
esctx.error_occurred is true or if the returned value is not 16.
>
> So I think it would be safer either to pre-validate that the 32 source
> characters are all hex digits before calling hex_decode_safe(), or to use a
> UUID-specific strict hex decoder for this path. After that, a comment
> explaining why hex_decode_safe() is safe here would make the invariant much
> clearer.
IIUC hex_decode_simd_helper() accepts only hex digits so we could
re-use it for UUID parsing. Let me check if the above idea of using
the return value works for us first.
>
> Could you also add a few regression tests for invalid inputs that contain
> whitespace inside otherwise fast-path-looking UUID strings? For example:
>
> ---------------------------------------------------------------
>
> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> ---------------------------------------------------------------
>
> These should continue to be rejected in the same way as the scalar parser.
> Regards,
Agreed.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-29 02:20 Haibo Yan <tristan.yim@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Haibo Yan @ 2026-06-29 02:20 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: pgsql-hackers
On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >
> >
> >
> > On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>
> >> Hi all,
> >>
> >> I'd like to propose the $subject.
> >>
> >> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> >> a run of hex digits is now fast. The attached patch reuses
> >> hex_decode_safe() in the UUID input function to speed up parsing.
> >>
> >> We accept several textual forms of a UUID[1]. The fast path handles
> >> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> >> (where "nx" means n hex digits), and either of those wrapped in
> >> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> >>
> >> I've benchmarked the parse speed using the following query:
> >>
> >> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> >> generate_series(1, 1000000);
> >> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> >>
> >> I compared the execution time of the second query, which measures
> >> uuid_in() alone, with/without SIMD optimization. Here are results (the
> >> median of 5 runs):
> >>
> >> HEAD: 208.879 ms
> >> Patched: 40.983 ms
> >>
> >> The improvements look promising to me. But in a realistic pipeline the
> >> parse is a small fraction of the work, so end-to-end gains could be
> >> much smaller.
> >>
> >> Feedback is very welcome.
> >>
> > I may be missing something, but I wonder whether the fast path is relying on
> > slightly different input semantics from the existing UUID parser.
> >
> > In particular, hex_decode_safe() is not a strict “32 hex characters only”
> > decoder. It skips whitespace, which is fine for its existing callers, but I
> > don’t think UUID input should treat whitespace inside the UUID body as
> > ignorable.
>
> Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> the following UUID value, which is bad:
>
> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> uuid
> --------------------------------------
> 019f00b5-7f8a-722f-b707-59f0ed25cd00
> (1 row)
>
> > Also, since hex_decode_safe() returns void, the UUID fast path
> > cannot verify that exactly UUID_LEN bytes were produced.
>
> IIUC hex_decode_safe() does return the output length in bytes. So I
> think we can fallback to the scalar UUID parser if
> esctx.error_occurred is true or if the returned value is not 16.
>
You’re right, I misread that part. Checking both esctx.error_occurred and
the returned length sounds good to me.
> >
> > So I think it would be safer either to pre-validate that the 32 source
> > characters are all hex digits before calling hex_decode_safe(), or to use a
> > UUID-specific strict hex decoder for this path. After that, a comment
> > explaining why hex_decode_safe() is safe here would make the invariant much
> > clearer.
>
> IIUC hex_decode_simd_helper() accepts only hex digits so we could
> re-use it for UUID parsing. Let me check if the above idea of using
> the return value works for us first.
>
That sounds reasonable. My main concern was to keep the fast path’s accepted
input set identical to the scalar UUID parser. Falling back when the decoded
length is not UUID_LEN, together with regression tests for whitespace cases,
should address that.
> >
> > Could you also add a few regression tests for invalid inputs that contain
> > whitespace inside otherwise fast-path-looking UUID strings? For example:
> >
> > ---------------------------------------------------------------
> >
> > SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > ---------------------------------------------------------------
> >
> > These should continue to be rejected in the same way as the scalar parser.
> > Regards,
>
> Agreed.
>
> Regards,
>
> --
> Masahiko Sawada
> Amazon Web Services: https://aws.amazon.com
Thanks!
Regards,
Haibo
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-29 21:54 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
0 siblings, 0 replies; 27+ messages in thread
From: Masahiko Sawada @ 2026-06-29 21:54 UTC (permalink / raw)
To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: pgsql-hackers
On Thu, Jun 25, 2026 at 12:30 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
>
> Hi,
>
> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >
> > Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> > a run of hex digits is now fast. The attached patch reuses
> > hex_decode_safe() in the UUID input function to speed up parsing.
> >
> > We accept several textual forms of a UUID[1]. The fast path handles
> > the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> > (where "nx" means n hex digits), and either of those wrapped in
> > braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> >
> > I've benchmarked the parse speed using the following query:
> >
> > CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> > generate_series(1, 1000000);
> > EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> >
> > I compared the execution time of the second query, which measures
> > uuid_in() alone, with/without SIMD optimization. Here are results (the
> > median of 5 runs):
> >
> > HEAD: 208.879 ms
> > Patched: 40.983 ms
>
> Nice!
>
> > The improvements look promising to me. But in a realistic pipeline the
> > parse is a small fraction of the work, so end-to-end gains could be
> > much smaller.
> >
> > Feedback is very welcome.
>
> I had a quick look at the patch. It mostly looks good to me. I like
> the idea of falling back to the scalar path when an error occurs - a
> neat user experience.
>
> I think it's not worth adding a test case for this because I believe
> this code gets covered anyway from existing tests.
>
> A few comments:
>
> 1/
> + * pass the local esctx instead of escontext to hex_decode_safe() to
>
> Instead of using variable names in the comments, let's say something
> like: we pass a separate error context to detect errors in the SIMD
> path and fall back to the normal path instead of raising ERRORs, for a
> better user experience.
Agreed.
>
> 2/
> + if (esctx.error_occurred)
> + string_to_uuid_scalar(source, uuid, escontext);
>
> An error on supported platforms seems rare, but when one occurs, I
> think it's worth emitting a WARNING or LOG message. This way the query
> succeeds, but later in the server logs, if noticed, it could provide
> useful reasoning or uncover issues in the SIMD code.
Hmm, I'm concerned that emitting a WARNING or LOG message whenever
parsing uncommon UUID forms might be annoying rather useful.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-29 21:55 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Haibo Yan <tristan.yim@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Masahiko Sawada @ 2026-06-29 21:55 UTC (permalink / raw)
To: Haibo Yan <tristan.yim@gmail.com>; +Cc: pgsql-hackers
On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>
> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >
> > On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > >
> > >
> > >
> > > On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >>
> > >> Hi all,
> > >>
> > >> I'd like to propose the $subject.
> > >>
> > >> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> > >> a run of hex digits is now fast. The attached patch reuses
> > >> hex_decode_safe() in the UUID input function to speed up parsing.
> > >>
> > >> We accept several textual forms of a UUID[1]. The fast path handles
> > >> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> > >> (where "nx" means n hex digits), and either of those wrapped in
> > >> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> > >>
> > >> I've benchmarked the parse speed using the following query:
> > >>
> > >> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> > >> generate_series(1, 1000000);
> > >> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> > >>
> > >> I compared the execution time of the second query, which measures
> > >> uuid_in() alone, with/without SIMD optimization. Here are results (the
> > >> median of 5 runs):
> > >>
> > >> HEAD: 208.879 ms
> > >> Patched: 40.983 ms
> > >>
> > >> The improvements look promising to me. But in a realistic pipeline the
> > >> parse is a small fraction of the work, so end-to-end gains could be
> > >> much smaller.
> > >>
> > >> Feedback is very welcome.
> > >>
> > > I may be missing something, but I wonder whether the fast path is relying on
> > > slightly different input semantics from the existing UUID parser.
> > >
> > > In particular, hex_decode_safe() is not a strict “32 hex characters only”
> > > decoder. It skips whitespace, which is fine for its existing callers, but I
> > > don’t think UUID input should treat whitespace inside the UUID body as
> > > ignorable.
> >
> > Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> > the following UUID value, which is bad:
> >
> > select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> > uuid
> > --------------------------------------
> > 019f00b5-7f8a-722f-b707-59f0ed25cd00
> > (1 row)
> >
> > > Also, since hex_decode_safe() returns void, the UUID fast path
> > > cannot verify that exactly UUID_LEN bytes were produced.
> >
> > IIUC hex_decode_safe() does return the output length in bytes. So I
> > think we can fallback to the scalar UUID parser if
> > esctx.error_occurred is true or if the returned value is not 16.
> >
>
> You’re right, I misread that part. Checking both esctx.error_occurred and
> the returned length sounds good to me.
>
> > >
> > > So I think it would be safer either to pre-validate that the 32 source
> > > characters are all hex digits before calling hex_decode_safe(), or to use a
> > > UUID-specific strict hex decoder for this path. After that, a comment
> > > explaining why hex_decode_safe() is safe here would make the invariant much
> > > clearer.
> >
> > IIUC hex_decode_simd_helper() accepts only hex digits so we could
> > re-use it for UUID parsing. Let me check if the above idea of using
> > the return value works for us first.
> >
>
> That sounds reasonable. My main concern was to keep the fast path’s accepted
> input set identical to the scalar UUID parser. Falling back when the decoded
> length is not UUID_LEN, together with regression tests for whitespace cases,
> should address that.
>
> > >
> > > Could you also add a few regression tests for invalid inputs that contain
> > > whitespace inside otherwise fast-path-looking UUID strings? For example:
> > >
> > > ---------------------------------------------------------------
> > >
> > > SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > > SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > > SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > > SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > > ---------------------------------------------------------------
> > >
> > > These should continue to be rejected in the same way as the scalar parser.
> > > Regards,
> >
> > Agreed.
> >
I've attached the updated patch.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-patch] v2-0001-Optimize-UUID-parse-using-SIMD.patch (8.3K, ../../CAD21AoC-M3HFaQ-DCXSKRgMceH=mDPXWdCSz22SG6HM=po-w5Q@mail.gmail.com/2-v2-0001-Optimize-UUID-parse-using-SIMD.patch)
download | inline diff:
From 7e8055206ce3c3abef611028c1dfdca1d4fde0c0 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 25 Jun 2026 10:03:44 -0700
Subject: [PATCH v2] Optimize UUID parse using SIMD.
Author:
Reviewed-by:
Discussion: https://postgr.es/m/
---
src/backend/utils/adt/uuid.c | 102 +++++++++++++++++++++++++++--
src/test/regress/expected/uuid.out | 55 ++++++++++++++++
src/test/regress/sql/uuid.sql | 16 +++++
3 files changed, 168 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 6ee3752ac78..6e7b841bde4 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -19,7 +19,9 @@
#include "common/hashfn.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
#include "port/pg_bswap.h"
+#include "utils/builtins.h"
#include "utils/fmgrprotos.h"
#include "utils/guc.h"
#include "utils/skipsupport.h"
@@ -122,13 +124,10 @@ uuid_out(PG_FUNCTION_ARGS)
}
/*
- * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
- * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
- * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
- * digits, is the only one used for output.)
+ * General UUID parser.
*/
static void
-string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+string_to_uuid_scalar(const char *source, pg_uuid_t *uuid, Node *escontext)
{
const char *src = source;
bool braces = false;
@@ -177,6 +176,99 @@ syntax_error:
"uuid", source)));
}
+/*
+ * Fast path for the common UUID shapes, built on our SIMD-aware hex decoder.
+ *
+ * This handles a bare string of 32 hex digits and the canonical
+ * 8x-4x-4x-4x-12x form (where "nx" means n hex digits), each optionally
+ * wrapped in braces. Any other shape, or any decoding error, is handed off to
+ * string_to_uuid_scalar() so that parsing and error reporting stay identical
+ * to the scalar implmentation.
+ */
+#ifndef USE_NO_SIMD
+static void
+string_to_uuid_fast(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+ const char *body = source;
+ size_t len = strlen(source);
+ const char *hexsrc = NULL;
+ char hexbuf[32];
+ uint64 written;
+ ErrorSaveContext esctx = {T_ErrorSaveContext};
+
+ /* Strip one optional surrounding brace pair */
+ if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
+ {
+ body = source + 1;
+ len -= 2;
+ }
+
+ if (len == 32)
+ {
+ /*
+ * Body is already 32 contiguous hex digits -- decode straight from
+ * the input. hex_decode_safe() reads exactly body[0..31], so it never
+ * touches the trailing NULL or '}'.
+ */
+ hexsrc = body;
+ }
+ else if (len == 36 && body[8] == '-' && body[13] == '-' &&
+ body[18] == '-' && body[23] == '-')
+ {
+ /*
+ * Canonical 8x-4x-4x-4x-12x form; compact them into hexbuf with
+ * fixed-offset copies, dropping the dashes.
+ */
+ memcpy(&hexbuf[0], &body[0], 8);
+ memcpy(&hexbuf[8], &body[9], 4);
+ memcpy(&hexbuf[12], &body[14], 4);
+ memcpy(&hexbuf[16], &body[19], 4);
+ memcpy(&hexbuf[20], &body[24], 12);
+ hexsrc = hexbuf;
+ }
+
+ if (hexsrc == NULL)
+ {
+ /* Uncommon shape; let the general parse handle it */
+ string_to_uuid_scalar(source, uuid, escontext);
+ return;
+ }
+
+ /*
+ * Decode the UUID hex data using our hex decoder that is SIMD-aware. We
+ * give it a private error context so that a decode failure is swalled
+ * here and reported by the scalar path instead, kepping the error message
+ * identical.
+ */
+ written = hex_decode_safe(hexsrc, 32, (char *) uuid->data, (Node *) &esctx);
+
+ /*
+ * Fall back to the scalar path on any error. We must also reject a short
+ * result: hex_decode_safe() skips whitespaces, so it can succeed yet
+ * write fewer than UUID_LEN bytes, whereas the UUID grammer forbids
+ * whitespaces.
+ */
+ if (esctx.error_occurred || written != UUID_LEN)
+ string_to_uuid_scalar(source, uuid, escontext);
+}
+#endif
+
+/*
+ * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
+ * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
+ * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
+ * digits, is the only one used for output.)
+ */
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+#ifdef USE_NO_SIMD
+ string_to_uuid_scalar(source, uuid, escontext);
+#else
+ string_to_uuid_fast(source, uuid, escontext);
+#endif
+}
+
Datum
uuid_recv(PG_FUNCTION_ARGS)
{
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index 9c5dda9e9ab..928e71c7ad3 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -340,5 +340,60 @@ SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
t
(1 row)
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f32 99c6770"
+LINE 1: SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ ^
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c67 "
+LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ ^
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: " 35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "AZ35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a-7143-4912-9b55-f322699c6770}"
+LINE 1: SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c6770}"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c67 }"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ ^
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
index 8cc2ad40614..d67d3d2ded9 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -161,5 +161,21 @@ SELECT '\x019a2f859ced7225b99d9c55044a2563'::bytea::uuid;
SELECT '\x1234567890abcdef'::bytea::uuid; -- error
SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
--
2.54.0
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-30 00:53 Haibo Yan <tristan.yim@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Haibo Yan @ 2026-06-30 00:53 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: pgsql-hackers
On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >
> > On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >
> > > On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > >
> > > >
> > > >
> > > > On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >>
> > > >> Hi all,
> > > >>
> > > >> I'd like to propose the $subject.
> > > >>
> > > >> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> > > >> a run of hex digits is now fast. The attached patch reuses
> > > >> hex_decode_safe() in the UUID input function to speed up parsing.
> > > >>
> > > >> We accept several textual forms of a UUID[1]. The fast path handles
> > > >> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> > > >> (where "nx" means n hex digits), and either of those wrapped in
> > > >> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> > > >>
> > > >> I've benchmarked the parse speed using the following query:
> > > >>
> > > >> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> > > >> generate_series(1, 1000000);
> > > >> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> > > >>
> > > >> I compared the execution time of the second query, which measures
> > > >> uuid_in() alone, with/without SIMD optimization. Here are results (the
> > > >> median of 5 runs):
> > > >>
> > > >> HEAD: 208.879 ms
> > > >> Patched: 40.983 ms
> > > >>
> > > >> The improvements look promising to me. But in a realistic pipeline the
> > > >> parse is a small fraction of the work, so end-to-end gains could be
> > > >> much smaller.
> > > >>
> > > >> Feedback is very welcome.
> > > >>
> > > > I may be missing something, but I wonder whether the fast path is relying on
> > > > slightly different input semantics from the existing UUID parser.
> > > >
> > > > In particular, hex_decode_safe() is not a strict “32 hex characters only”
> > > > decoder. It skips whitespace, which is fine for its existing callers, but I
> > > > don’t think UUID input should treat whitespace inside the UUID body as
> > > > ignorable.
> > >
> > > Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> > > the following UUID value, which is bad:
> > >
> > > select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> > > uuid
> > > --------------------------------------
> > > 019f00b5-7f8a-722f-b707-59f0ed25cd00
> > > (1 row)
> > >
> > > > Also, since hex_decode_safe() returns void, the UUID fast path
> > > > cannot verify that exactly UUID_LEN bytes were produced.
> > >
> > > IIUC hex_decode_safe() does return the output length in bytes. So I
> > > think we can fallback to the scalar UUID parser if
> > > esctx.error_occurred is true or if the returned value is not 16.
> > >
> >
> > You’re right, I misread that part. Checking both esctx.error_occurred and
> > the returned length sounds good to me.
> >
> > > >
> > > > So I think it would be safer either to pre-validate that the 32 source
> > > > characters are all hex digits before calling hex_decode_safe(), or to use a
> > > > UUID-specific strict hex decoder for this path. After that, a comment
> > > > explaining why hex_decode_safe() is safe here would make the invariant much
> > > > clearer.
> > >
> > > IIUC hex_decode_simd_helper() accepts only hex digits so we could
> > > re-use it for UUID parsing. Let me check if the above idea of using
> > > the return value works for us first.
> > >
> >
> > That sounds reasonable. My main concern was to keep the fast path’s accepted
> > input set identical to the scalar UUID parser. Falling back when the decoded
> > length is not UUID_LEN, together with regression tests for whitespace cases,
> > should address that.
> >
> > > >
> > > > Could you also add a few regression tests for invalid inputs that contain
> > > > whitespace inside otherwise fast-path-looking UUID strings? For example:
> > > >
> > > > ---------------------------------------------------------------
> > > >
> > > > SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > > > SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > > > SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > > > SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > > > ---------------------------------------------------------------
> > > >
> > > > These should continue to be rejected in the same way as the scalar parser.
> > > > Regards,
> > >
> > > Agreed.
> > >
>
> I've attached the updated patch.
>
> Regards,
>
> --
> Masahiko Sawada
> Amazon Web Services: https://aws.amazon.com
I noticed a few typos in the comments:
src/backend/utils/adt/uuid.c
line 56: “scalar implmentation” -> “scalar implementation”
line 109: “swalled” -> “swallowed”
line 110: “kepping” -> “keeping”
line 118: “grammer” -> “grammar”
line 119: “whitespaces” -> “whitespace”
Could you fix them ?
Thank you.
Regards.
Haibo
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-30 17:53 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Haibo Yan <tristan.yim@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Masahiko Sawada @ 2026-06-30 17:53 UTC (permalink / raw)
To: Haibo Yan <tristan.yim@gmail.com>; +Cc: pgsql-hackers
On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>
> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >
> > On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > >
> > > On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >
> > > > On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > > >
> > > > >
> > > > >
> > > > > On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > > >>
> > > > >> Hi all,
> > > > >>
> > > > >> I'd like to propose the $subject.
> > > > >>
> > > > >> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> > > > >> a run of hex digits is now fast. The attached patch reuses
> > > > >> hex_decode_safe() in the UUID input function to speed up parsing.
> > > > >>
> > > > >> We accept several textual forms of a UUID[1]. The fast path handles
> > > > >> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> > > > >> (where "nx" means n hex digits), and either of those wrapped in
> > > > >> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> > > > >>
> > > > >> I've benchmarked the parse speed using the following query:
> > > > >>
> > > > >> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> > > > >> generate_series(1, 1000000);
> > > > >> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> > > > >>
> > > > >> I compared the execution time of the second query, which measures
> > > > >> uuid_in() alone, with/without SIMD optimization. Here are results (the
> > > > >> median of 5 runs):
> > > > >>
> > > > >> HEAD: 208.879 ms
> > > > >> Patched: 40.983 ms
> > > > >>
> > > > >> The improvements look promising to me. But in a realistic pipeline the
> > > > >> parse is a small fraction of the work, so end-to-end gains could be
> > > > >> much smaller.
> > > > >>
> > > > >> Feedback is very welcome.
> > > > >>
> > > > > I may be missing something, but I wonder whether the fast path is relying on
> > > > > slightly different input semantics from the existing UUID parser.
> > > > >
> > > > > In particular, hex_decode_safe() is not a strict “32 hex characters only”
> > > > > decoder. It skips whitespace, which is fine for its existing callers, but I
> > > > > don’t think UUID input should treat whitespace inside the UUID body as
> > > > > ignorable.
> > > >
> > > > Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> > > > the following UUID value, which is bad:
> > > >
> > > > select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> > > > uuid
> > > > --------------------------------------
> > > > 019f00b5-7f8a-722f-b707-59f0ed25cd00
> > > > (1 row)
> > > >
> > > > > Also, since hex_decode_safe() returns void, the UUID fast path
> > > > > cannot verify that exactly UUID_LEN bytes were produced.
> > > >
> > > > IIUC hex_decode_safe() does return the output length in bytes. So I
> > > > think we can fallback to the scalar UUID parser if
> > > > esctx.error_occurred is true or if the returned value is not 16.
> > > >
> > >
> > > You’re right, I misread that part. Checking both esctx.error_occurred and
> > > the returned length sounds good to me.
> > >
> > > > >
> > > > > So I think it would be safer either to pre-validate that the 32 source
> > > > > characters are all hex digits before calling hex_decode_safe(), or to use a
> > > > > UUID-specific strict hex decoder for this path. After that, a comment
> > > > > explaining why hex_decode_safe() is safe here would make the invariant much
> > > > > clearer.
> > > >
> > > > IIUC hex_decode_simd_helper() accepts only hex digits so we could
> > > > re-use it for UUID parsing. Let me check if the above idea of using
> > > > the return value works for us first.
> > > >
> > >
> > > That sounds reasonable. My main concern was to keep the fast path’s accepted
> > > input set identical to the scalar UUID parser. Falling back when the decoded
> > > length is not UUID_LEN, together with regression tests for whitespace cases,
> > > should address that.
> > >
> > > > >
> > > > > Could you also add a few regression tests for invalid inputs that contain
> > > > > whitespace inside otherwise fast-path-looking UUID strings? For example:
> > > > >
> > > > > ---------------------------------------------------------------
> > > > >
> > > > > SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > > > > SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > > > > SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > > > > SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > > > > ---------------------------------------------------------------
> > > > >
> > > > > These should continue to be rejected in the same way as the scalar parser.
> > > > > Regards,
> > > >
> > > > Agreed.
> > > >
> >
> > I've attached the updated patch.
> >
> > Regards,
> >
> > --
> > Masahiko Sawada
> > Amazon Web Services: https://aws.amazon.com
>
> I noticed a few typos in the comments:
>
> src/backend/utils/adt/uuid.c
> line 56: “scalar implmentation” -> “scalar implementation”
> line 109: “swalled” -> “swallowed”
> line 110: “kepping” -> “keeping”
> line 118: “grammer” -> “grammar”
> line 119: “whitespaces” -> “whitespace”
>
> Could you fix them ?
Oops, I fixed them and rechecked other places.
I've attached the updated patch.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-patch] v3-0001-Optimize-UUID-parse-using-SIMD.patch (9.3K, ../../CAD21AoB+gJALda3UPGvZMXAeFjF_4qrNoHVfwvF-uj8L19hnPA@mail.gmail.com/2-v3-0001-Optimize-UUID-parse-using-SIMD.patch)
download | inline diff:
From 8d9ae434c2924a2cea14f521f6c3f2e9b168c0fe Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 25 Jun 2026 10:03:44 -0700
Subject: [PATCH v3] Optimize UUID parse using SIMD.
string_to_uuid() parsed the input one character at a time in a scalar
loop. Add a fast path that recognizes the two common shapes -- a bare
string of 32 hexadecimal digits and the canonical 8x-4x-4x-4x-12x
form, each optionally wrapped in braces -- compacts them into 32
contiguous hex digits, and decodes them with the SIMD-aware
hex_decode_safe().
Any other shape, or any decoding error, is handed off to the original
scalar parser (now string_to_uuid_scalar()), so the accepted grammar
and the error messages are unchanged. In particular,
hex_decode_safe() silently skips whitespace while the UUID grammar
does not, so the fast path also rejects results shorter than UUID_LEN
and falls back to the scalar parser, which reports the error.
Regression tests are added for the shapes handled by the fast path and
for invalid inputs that must still be rejected.
Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviwed-by: Haibo Yan <tristan.yim@gmail.com>
Discussion: https://postgr.es/m/CAD21AoCqeR4UQU77Q_yOMNNzJ7AVeiO5QZT+4HnzPm4Wm-e02Q@mail.gmail.com
---
src/backend/utils/adt/uuid.c | 101 +++++++++++++++++++++++++++--
src/test/regress/expected/uuid.out | 55 ++++++++++++++++
src/test/regress/sql/uuid.sql | 16 +++++
3 files changed, 167 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 6ee3752ac78..bbfecd26d49 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -19,7 +19,9 @@
#include "common/hashfn.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
#include "port/pg_bswap.h"
+#include "utils/builtins.h"
#include "utils/fmgrprotos.h"
#include "utils/guc.h"
#include "utils/skipsupport.h"
@@ -122,13 +124,10 @@ uuid_out(PG_FUNCTION_ARGS)
}
/*
- * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
- * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
- * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
- * digits, is the only one used for output.)
+ * General UUID parser.
*/
static void
-string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+string_to_uuid_scalar(const char *source, pg_uuid_t *uuid, Node *escontext)
{
const char *src = source;
bool braces = false;
@@ -177,6 +176,98 @@ syntax_error:
"uuid", source)));
}
+/*
+ * Fast path for the common UUID shapes, built on our SIMD-aware hex decoder.
+ *
+ * This handles a bare string of 32 hex digits and the canonical
+ * 8x-4x-4x-4x-12x form (where "nx" means n hex digits), each optionally
+ * wrapped in braces. Any other shape, or any decoding error, is handed off to
+ * string_to_uuid_scalar() so that parsing and error reporting stay identical
+ * to the scalar implementation.
+ */
+#ifndef USE_NO_SIMD
+static void
+string_to_uuid_fast(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+ const char *body = source;
+ size_t len = strlen(source);
+ const char *hexsrc = NULL;
+ char hexbuf[32];
+ uint64 written;
+ ErrorSaveContext esctx = {T_ErrorSaveContext};
+
+ /* Strip one optional surrounding brace pair */
+ if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
+ {
+ body = source + 1;
+ len -= 2;
+ }
+
+ if (len == 32)
+ {
+ /*
+ * Body is already 32 contiguous hex digits -- decode straight from
+ * the input. hex_decode_safe() reads exactly body[0..31], so it never
+ * touches the trailing NULL or '}'.
+ */
+ hexsrc = body;
+ }
+ else if (len == 36 && body[8] == '-' && body[13] == '-' &&
+ body[18] == '-' && body[23] == '-')
+ {
+ /*
+ * Canonical 8x-4x-4x-4x-12x form; compact them into hexbuf with
+ * fixed-offset copies, dropping the dashes.
+ */
+ memcpy(&hexbuf[0], &body[0], 8);
+ memcpy(&hexbuf[8], &body[9], 4);
+ memcpy(&hexbuf[12], &body[14], 4);
+ memcpy(&hexbuf[16], &body[19], 4);
+ memcpy(&hexbuf[20], &body[24], 12);
+ hexsrc = hexbuf;
+ }
+
+ if (hexsrc == NULL)
+ {
+ /* Uncommon shape; let the general parse handle it */
+ string_to_uuid_scalar(source, uuid, escontext);
+ return;
+ }
+
+ /*
+ * Decode the UUID hex data using our hex decoder that is SIMD-aware. We
+ * give it a private error context so that a decode failure is swallowed
+ * here and reported by the scalar path instead, keeping the error message
+ * identical.
+ */
+ written = hex_decode_safe(hexsrc, 32, (char *) uuid->data, (Node *) &esctx);
+
+ /*
+ * Fall back to the scalar path on any error. We must also reject a short
+ * result: hex_decode_safe() skips whitespace, so it can succeed yet write
+ * fewer than UUID_LEN bytes, whereas the UUID grammar forbids whitespace.
+ */
+ if (esctx.error_occurred || written != UUID_LEN)
+ string_to_uuid_scalar(source, uuid, escontext);
+}
+#endif
+
+/*
+ * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
+ * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
+ * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
+ * digits, is the only one used for output.)
+ */
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+#ifdef USE_NO_SIMD
+ string_to_uuid_scalar(source, uuid, escontext);
+#else
+ string_to_uuid_fast(source, uuid, escontext);
+#endif
+}
+
Datum
uuid_recv(PG_FUNCTION_ARGS)
{
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index 9c5dda9e9ab..928e71c7ad3 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -340,5 +340,60 @@ SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
t
(1 row)
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f32 99c6770"
+LINE 1: SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ ^
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c67 "
+LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ ^
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: " 35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "AZ35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a-7143-4912-9b55-f322699c6770}"
+LINE 1: SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c6770}"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c67 }"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ ^
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
index 8cc2ad40614..d67d3d2ded9 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -161,5 +161,21 @@ SELECT '\x019a2f859ced7225b99d9c55044a2563'::bytea::uuid;
SELECT '\x1234567890abcdef'::bytea::uuid; -- error
SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
--
2.54.0
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-06-30 18:03 Haibo Yan <tristan.yim@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Haibo Yan @ 2026-06-30 18:03 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: pgsql-hackers
On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >
> > On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >
> > > On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > >
> > > > On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > > >
> > > > > On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > > > >
> > > > > >
> > > > > >
> > > > > > On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > > > >>
> > > > > >> Hi all,
> > > > > >>
> > > > > >> I'd like to propose the $subject.
> > > > > >>
> > > > > >> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> > > > > >> a run of hex digits is now fast. The attached patch reuses
> > > > > >> hex_decode_safe() in the UUID input function to speed up parsing.
> > > > > >>
> > > > > >> We accept several textual forms of a UUID[1]. The fast path handles
> > > > > >> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> > > > > >> (where "nx" means n hex digits), and either of those wrapped in
> > > > > >> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> > > > > >>
> > > > > >> I've benchmarked the parse speed using the following query:
> > > > > >>
> > > > > >> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> > > > > >> generate_series(1, 1000000);
> > > > > >> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> > > > > >>
> > > > > >> I compared the execution time of the second query, which measures
> > > > > >> uuid_in() alone, with/without SIMD optimization. Here are results (the
> > > > > >> median of 5 runs):
> > > > > >>
> > > > > >> HEAD: 208.879 ms
> > > > > >> Patched: 40.983 ms
> > > > > >>
> > > > > >> The improvements look promising to me. But in a realistic pipeline the
> > > > > >> parse is a small fraction of the work, so end-to-end gains could be
> > > > > >> much smaller.
> > > > > >>
> > > > > >> Feedback is very welcome.
> > > > > >>
> > > > > > I may be missing something, but I wonder whether the fast path is relying on
> > > > > > slightly different input semantics from the existing UUID parser.
> > > > > >
> > > > > > In particular, hex_decode_safe() is not a strict “32 hex characters only”
> > > > > > decoder. It skips whitespace, which is fine for its existing callers, but I
> > > > > > don’t think UUID input should treat whitespace inside the UUID body as
> > > > > > ignorable.
> > > > >
> > > > > Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> > > > > the following UUID value, which is bad:
> > > > >
> > > > > select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> > > > > uuid
> > > > > --------------------------------------
> > > > > 019f00b5-7f8a-722f-b707-59f0ed25cd00
> > > > > (1 row)
> > > > >
> > > > > > Also, since hex_decode_safe() returns void, the UUID fast path
> > > > > > cannot verify that exactly UUID_LEN bytes were produced.
> > > > >
> > > > > IIUC hex_decode_safe() does return the output length in bytes. So I
> > > > > think we can fallback to the scalar UUID parser if
> > > > > esctx.error_occurred is true or if the returned value is not 16.
> > > > >
> > > >
> > > > You’re right, I misread that part. Checking both esctx.error_occurred and
> > > > the returned length sounds good to me.
> > > >
> > > > > >
> > > > > > So I think it would be safer either to pre-validate that the 32 source
> > > > > > characters are all hex digits before calling hex_decode_safe(), or to use a
> > > > > > UUID-specific strict hex decoder for this path. After that, a comment
> > > > > > explaining why hex_decode_safe() is safe here would make the invariant much
> > > > > > clearer.
> > > > >
> > > > > IIUC hex_decode_simd_helper() accepts only hex digits so we could
> > > > > re-use it for UUID parsing. Let me check if the above idea of using
> > > > > the return value works for us first.
> > > > >
> > > >
> > > > That sounds reasonable. My main concern was to keep the fast path’s accepted
> > > > input set identical to the scalar UUID parser. Falling back when the decoded
> > > > length is not UUID_LEN, together with regression tests for whitespace cases,
> > > > should address that.
> > > >
> > > > > >
> > > > > > Could you also add a few regression tests for invalid inputs that contain
> > > > > > whitespace inside otherwise fast-path-looking UUID strings? For example:
> > > > > >
> > > > > > ---------------------------------------------------------------
> > > > > >
> > > > > > SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > > > > > SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > > > > > SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > > > > > SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > > > > > ---------------------------------------------------------------
> > > > > >
> > > > > > These should continue to be rejected in the same way as the scalar parser.
> > > > > > Regards,
> > > > >
> > > > > Agreed.
> > > > >
> > >
> > > I've attached the updated patch.
> > >
> > > Regards,
> > >
> > > --
> > > Masahiko Sawada
> > > Amazon Web Services: https://aws.amazon.com
> >
> > I noticed a few typos in the comments:
> >
> > src/backend/utils/adt/uuid.c
> > line 56: “scalar implmentation” -> “scalar implementation”
> > line 109: “swalled” -> “swallowed”
> > line 110: “kepping” -> “keeping”
> > line 118: “grammer” -> “grammar”
> > line 119: “whitespaces” -> “whitespace”
> >
> > Could you fix them ?
>
> Oops, I fixed them and rechecked other places.
>
> I've attached the updated patch.
>
> Regards,
>
> --
> Masahiko Sawada
> Amazon Web Services: https://aws.amazon.com
The code looks good to me now. I only noticed one small typo in the
commit trailer: Reviwed-by should be Reviewed-by.
Otherwise, it looks good. Thank you for fixing these issues.
Regards,
Haibo
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-06 00:19 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Haibo Yan <tristan.yim@gmail.com>
0 siblings, 2 replies; 27+ messages in thread
From: Masahiko Sawada @ 2026-08-06 00:19 UTC (permalink / raw)
To: Haibo Yan <tristan.yim@gmail.com>; +Cc: pgsql-hackers
On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <tristan.yim@gmail.com> wrote:
>
> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >
> > On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > >
> > > On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >
> > > > On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > > >
> > > > > On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > > > >
> > > > > > On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > > > > >
> > > > > > >
> > > > > > >
> > > > > > > On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > > > > >>
> > > > > > >> Hi all,
> > > > > > >>
> > > > > > >> I'd like to propose the $subject.
> > > > > > >>
> > > > > > >> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> > > > > > >> a run of hex digits is now fast. The attached patch reuses
> > > > > > >> hex_decode_safe() in the UUID input function to speed up parsing.
> > > > > > >>
> > > > > > >> We accept several textual forms of a UUID[1]. The fast path handles
> > > > > > >> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> > > > > > >> (where "nx" means n hex digits), and either of those wrapped in
> > > > > > >> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> > > > > > >>
> > > > > > >> I've benchmarked the parse speed using the following query:
> > > > > > >>
> > > > > > >> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> > > > > > >> generate_series(1, 1000000);
> > > > > > >> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> > > > > > >>
> > > > > > >> I compared the execution time of the second query, which measures
> > > > > > >> uuid_in() alone, with/without SIMD optimization. Here are results (the
> > > > > > >> median of 5 runs):
> > > > > > >>
> > > > > > >> HEAD: 208.879 ms
> > > > > > >> Patched: 40.983 ms
> > > > > > >>
> > > > > > >> The improvements look promising to me. But in a realistic pipeline the
> > > > > > >> parse is a small fraction of the work, so end-to-end gains could be
> > > > > > >> much smaller.
> > > > > > >>
> > > > > > >> Feedback is very welcome.
> > > > > > >>
> > > > > > > I may be missing something, but I wonder whether the fast path is relying on
> > > > > > > slightly different input semantics from the existing UUID parser.
> > > > > > >
> > > > > > > In particular, hex_decode_safe() is not a strict “32 hex characters only”
> > > > > > > decoder. It skips whitespace, which is fine for its existing callers, but I
> > > > > > > don’t think UUID input should treat whitespace inside the UUID body as
> > > > > > > ignorable.
> > > > > >
> > > > > > Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> > > > > > the following UUID value, which is bad:
> > > > > >
> > > > > > select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> > > > > > uuid
> > > > > > --------------------------------------
> > > > > > 019f00b5-7f8a-722f-b707-59f0ed25cd00
> > > > > > (1 row)
> > > > > >
> > > > > > > Also, since hex_decode_safe() returns void, the UUID fast path
> > > > > > > cannot verify that exactly UUID_LEN bytes were produced.
> > > > > >
> > > > > > IIUC hex_decode_safe() does return the output length in bytes. So I
> > > > > > think we can fallback to the scalar UUID parser if
> > > > > > esctx.error_occurred is true or if the returned value is not 16.
> > > > > >
> > > > >
> > > > > You’re right, I misread that part. Checking both esctx.error_occurred and
> > > > > the returned length sounds good to me.
> > > > >
> > > > > > >
> > > > > > > So I think it would be safer either to pre-validate that the 32 source
> > > > > > > characters are all hex digits before calling hex_decode_safe(), or to use a
> > > > > > > UUID-specific strict hex decoder for this path. After that, a comment
> > > > > > > explaining why hex_decode_safe() is safe here would make the invariant much
> > > > > > > clearer.
> > > > > >
> > > > > > IIUC hex_decode_simd_helper() accepts only hex digits so we could
> > > > > > re-use it for UUID parsing. Let me check if the above idea of using
> > > > > > the return value works for us first.
> > > > > >
> > > > >
> > > > > That sounds reasonable. My main concern was to keep the fast path’s accepted
> > > > > input set identical to the scalar UUID parser. Falling back when the decoded
> > > > > length is not UUID_LEN, together with regression tests for whitespace cases,
> > > > > should address that.
> > > > >
> > > > > > >
> > > > > > > Could you also add a few regression tests for invalid inputs that contain
> > > > > > > whitespace inside otherwise fast-path-looking UUID strings? For example:
> > > > > > >
> > > > > > > ---------------------------------------------------------------
> > > > > > >
> > > > > > > SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > > > > > > SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > > > > > > SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > > > > > > SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > > > > > > ---------------------------------------------------------------
> > > > > > >
> > > > > > > These should continue to be rejected in the same way as the scalar parser.
> > > > > > > Regards,
> > > > > >
> > > > > > Agreed.
> > > > > >
> > > >
> > > > I've attached the updated patch.
> > > >
> > > > Regards,
> > > >
> > > > --
> > > > Masahiko Sawada
> > > > Amazon Web Services: https://aws.amazon.com
> > >
> > > I noticed a few typos in the comments:
> > >
> > > src/backend/utils/adt/uuid.c
> > > line 56: “scalar implmentation” -> “scalar implementation”
> > > line 109: “swalled” -> “swallowed”
> > > line 110: “kepping” -> “keeping”
> > > line 118: “grammer” -> “grammar”
> > > line 119: “whitespaces” -> “whitespace”
> > >
> > > Could you fix them ?
> >
> > Oops, I fixed them and rechecked other places.
> >
> > I've attached the updated patch.
> >
> > Regards,
> >
> > --
> > Masahiko Sawada
> > Amazon Web Services: https://aws.amazon.com
>
> The code looks good to me now. I only noticed one small typo in the
> commit trailer: Reviwed-by should be Reviewed-by.
>
> Otherwise, it looks good. Thank you for fixing these issues.
>
After spending more time on this patch, I find out two things:
1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
But including port/simd.h seems wrong as it doesn't use any SIMD
support functions.
2. hex_decode_safe() is faster than the current UUID parse
(isxdigit()+strtoul() approach) even without SIMD. I've created a
small benchmark test tool (attached as 0002 patch, not intended to be
pushed into the core), and measures UUID parsing performance of three
approaches: 'scalar' is the current string_to_uuid() that uses
isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
UUIDs. Here are results:
=# select path, shape, n_inputs, best_ms::numeric(10,3) from
uuid_parse_bench(100000, 5);
path | shape | n_inputs | best_ms
--------+------------------+----------+---------
scalar | canonical | 100000 | 22.661
simd | canonical | 100000 | 1.400
nosimd | canonical | 100000 | 1.652
scalar | bare32 | 100000 | 15.932
simd | bare32 | 100000 | 0.471
nosimd | bare32 | 100000 | 1.110
scalar | braced_canonical | 100000 | 17.330
simd | braced_canonical | 100000 | 1.088
nosimd | braced_canonical | 100000 | 1.314
scalar | braced_bare32 | 100000 | 15.942
simd | braced_bare32 | 100000 | 0.488
nosimd | braced_bare32 | 100000 | 1.141
scalar | dashed4 | 100000 | 16.185
simd | dashed4 | 100000 | 16.493
nosimd | dashed4 | 100000 | 16.403
scalar | invalid_hex | 100000 | 0.199
simd | invalid_hex | 100000 | 1.150
nosimd | invalid_hex | 100000 | 0.385
(18 rows)
Each of shape means:
- 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
- 'bare32': 32 contiguous hex digits
- 'braced_canonical': {8x-4x-4x-4x-12x}
- 'braced_bare32': {32 hex digits}
- 'dashed4': dash after every group of 4
- 'invalid_hdx': canonical but with a invalid digit
'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
and 'nosimd' fall back to the 'scalar' case. According to these
results, my conclusion is that we can use hex_decode_safe() for
canonical forms and 32 contiguous hex forms anyway, and let
hex_decode_safe() choose whether to use SIMD. We would win in either
case. We still use the current scalar approach for uncommon UUID forms
and error reporting purposes.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-patch] v4-0001-Optimize-UUID-parse-using-SIMD.patch (10.0K, ../../CAD21AoBVZTkg_E+Bam7Df6KgCur+rhBL9+hWZqR+YMsAoVgbxw@mail.gmail.com/2-v4-0001-Optimize-UUID-parse-using-SIMD.patch)
download | inline diff:
From 9d2edad34a1823bab012f5d0593d461a9ec396fb Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 25 Jun 2026 10:03:44 -0700
Subject: [PATCH v4 1/2] Optimize UUID parse using SIMD.
Previously, string_to_uuid() decoded one byte at a time, calling
isxdigit() twice and strtoul() once for every pair of hexadecimal
digits. That loop dominated the cost of uuid_in().
This commit adds a fast path for the two common shapes: a bare string
of 32 hexadecimal digits, and the canonical 8x-4x-4x-4x-12x form
(where "nx" means n hexadecimal digits), each optionally wrapped in
braces. Both are compacted into 32 contiguous hexadecimal digits and
decoded with hex_decode_safe(). Any other shape, or any decoding
error, is handed off to the original scalar parser, now
string_to_uuid_scalar(), so the accepted grammar and the error
messages are unchanged.
hex_decode_safe() silently skips whitespace while the UUID grammar
does not, so a decode can succeed and still write fewer than UUID_LEN
bytes. The fast path therefore treats a short result as a failure,
just like an error, and lets the scalar parser reject the input and
report the syntax error.
The fast path is deliberately not conditional on SIMD support.
hex_decode_safe() selects a vectorized or scalar implementation
itself, and even its scalar implementation is an order of magnitude
faster than decoding a byte at a time, so gating this on USE_NO_SIMD
would only penalize platforms that have neither SSE2 nor NEON.
Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Discussion: https://postgr.es/m/CAD21AoCqeR4UQU77Q_yOMNNzJ7AVeiO5QZT+4HnzPm4Wm-e02Q@mail.gmail.com
---
src/backend/utils/adt/uuid.c | 94 ++++++++++++++++++++++++++++--
src/test/regress/expected/uuid.out | 55 +++++++++++++++++
src/test/regress/sql/uuid.sql | 16 +++++
3 files changed, 160 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 246225a8735..3ecca6d3748 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -19,7 +19,9 @@
#include "common/hashfn.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
#include "port/pg_bswap.h"
+#include "utils/builtins.h"
#include "utils/fmgrprotos.h"
#include "utils/guc.h"
#include "utils/skipsupport.h"
@@ -139,13 +141,13 @@ uuid_out(PG_FUNCTION_ARGS)
}
/*
- * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
- * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
- * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
- * digits, is the only one used for output.)
+ * Reference implementation of the UUID grammar, parsing one character at a
+ * time. string_to_uuid() recognizes the common shapes more cheaply and
+ * defers to this function for everything else, so this is also the only
+ * place that reports a syntax error.
*/
static void
-string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+string_to_uuid_scalar(const char *source, pg_uuid_t *uuid, Node *escontext)
{
const char *src = source;
bool braces = false;
@@ -194,6 +196,88 @@ syntax_error:
"uuid", source)));
}
+/*
+ * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
+ * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
+ * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
+ * digits, is the only one used for output.)
+ *
+ * The two common shapes -- a bare string of 32 hexadecimal digits and the
+ * canonical form, each optionally wrapped in braces -- are compacted into 32
+ * contiguous hex digits and decoded with hex_decode_safe(), which is much
+ * faster than the character-at-a-time loop. Any other shape, or any decoding
+ * error, is handed off to string_to_uuid_scalar() so that the accepted
+ * grammar and the error messages are unchanged.
+ *
+ * Note that this fast path is not conditional on SIMD support:
+ * hex_decode_safe() picks a vectorized or scalar implementation itself, and
+ * even its scalar implementation is far faster than string_to_uuid_scalar().
+ */
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+ const char *body = source;
+ size_t len = strlen(source);
+ const char *hexsrc = NULL;
+ char hexbuf[32];
+ uint64 written;
+ ErrorSaveContext esctx = {T_ErrorSaveContext};
+
+ /* Strip one optional surrounding brace pair */
+ if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
+ {
+ body = source + 1;
+ len -= 2;
+ }
+
+ if (len == 32)
+ {
+ /*
+ * Body is already 32 contiguous hex digits -- decode straight from
+ * the input. hex_decode_safe() reads exactly body[0..31], so it never
+ * touches the trailing NULL or '}'.
+ */
+ hexsrc = body;
+ }
+ else if (len == 36 && body[8] == '-' && body[13] == '-' &&
+ body[18] == '-' && body[23] == '-')
+ {
+ /*
+ * Canonical 8x-4x-4x-4x-12x form; compact them into hexbuf with
+ * fixed-offset copies, dropping the dashes.
+ */
+ memcpy(&hexbuf[0], &body[0], 8);
+ memcpy(&hexbuf[8], &body[9], 4);
+ memcpy(&hexbuf[12], &body[14], 4);
+ memcpy(&hexbuf[16], &body[19], 4);
+ memcpy(&hexbuf[20], &body[24], 12);
+ hexsrc = hexbuf;
+ }
+
+ if (hexsrc == NULL)
+ {
+ /* Uncommon shape; let the general parse handle it */
+ string_to_uuid_scalar(source, uuid, escontext);
+ return;
+ }
+
+ /*
+ * Decode the UUID hex data using our hex decoder that is SIMD-aware. We
+ * give it a private error context so that a decode failure is swallowed
+ * here and reported by the scalar path instead, keeping the error message
+ * identical.
+ */
+ written = hex_decode_safe(hexsrc, 32, (char *) uuid->data, (Node *) &esctx);
+
+ /*
+ * Fall back to the scalar path on any error. We must also reject a short
+ * result: hex_decode_safe() skips whitespace, so it can succeed yet write
+ * fewer than UUID_LEN bytes, whereas the UUID grammar forbids whitespace.
+ */
+ if (esctx.error_occurred || written != UUID_LEN)
+ string_to_uuid_scalar(source, uuid, escontext);
+}
+
Datum
uuid_recv(PG_FUNCTION_ARGS)
{
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index d542eb14b26..b40f50ac8be 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -375,5 +375,60 @@ SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
t
(1 row)
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f32 99c6770"
+LINE 1: SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ ^
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c67 "
+LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ ^
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: " 35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "AZ35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a-7143-4912-9b55-f322699c6770}"
+LINE 1: SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c6770}"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c67 }"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ ^
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
index 54f0d8f8255..bdbeb91fe4c 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -178,5 +178,21 @@ SELECT '\x019a2f859ced7225b99d9c55044a2563'::bytea::uuid;
SELECT '\x1234567890abcdef'::bytea::uuid; -- error
SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
--
2.55.0
[text/x-patch] v4-0002-uuid_parse_bench-module.patch (20.6K, ../../CAD21AoBVZTkg_E+Bam7Df6KgCur+rhBL9+hWZqR+YMsAoVgbxw@mail.gmail.com/3-v4-0002-uuid_parse_bench-module.patch)
download | inline diff:
From dc286d4772945d1141a19f447c889116042d5b47 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Wed, 5 Aug 2026 14:21:24 -0700
Subject: [PATCH v4 2/2] uuid_parse_bench module.
---
src/test/modules/uuid_parse_bench/Makefile | 23 +
src/test/modules/uuid_parse_bench/meson.build | 33 +
.../uuid_parse_bench--1.0.sql | 24 +
.../uuid_parse_bench/uuid_parse_bench.c | 641 ++++++++++++++++++
.../uuid_parse_bench/uuid_parse_bench.control | 5 +
5 files changed, 726 insertions(+)
create mode 100644 src/test/modules/uuid_parse_bench/Makefile
create mode 100644 src/test/modules/uuid_parse_bench/meson.build
create mode 100644 src/test/modules/uuid_parse_bench/uuid_parse_bench--1.0.sql
create mode 100644 src/test/modules/uuid_parse_bench/uuid_parse_bench.c
create mode 100644 src/test/modules/uuid_parse_bench/uuid_parse_bench.control
diff --git a/src/test/modules/uuid_parse_bench/Makefile b/src/test/modules/uuid_parse_bench/Makefile
new file mode 100644
index 00000000000..9919d7c8309
--- /dev/null
+++ b/src/test/modules/uuid_parse_bench/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/uuid_parse_bench/Makefile
+
+MODULE_big = uuid_parse_bench
+OBJS = \
+ $(WIN32RES) \
+ uuid_parse_bench.o
+PGFILEDESC = "uuid_parse_bench - microbenchmark for UUID input parsing"
+
+EXTENSION = uuid_parse_bench
+DATA = uuid_parse_bench--1.0.sql
+
+REGRESS = uuid_parse_bench
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/uuid_parse_bench
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/uuid_parse_bench/meson.build b/src/test/modules/uuid_parse_bench/meson.build
new file mode 100644
index 00000000000..8633ccd56ba
--- /dev/null
+++ b/src/test/modules/uuid_parse_bench/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2022-2026, PostgreSQL Global Development Group
+
+uuid_parse_bench_sources = files(
+ 'uuid_parse_bench.c',
+)
+
+if host_system == 'windows'
+ uuid_parse_bench_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'uuid_parse_bench',
+ '--FILEDESC', 'uuid_parse_bench - microbenchmark for UUID input parsing',])
+endif
+
+uuid_parse_bench = shared_module('uuid_parse_bench',
+ uuid_parse_bench_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += uuid_parse_bench
+
+test_install_data += files(
+ 'uuid_parse_bench.control',
+ 'uuid_parse_bench--1.0.sql',
+)
+
+tests += {
+ 'name': 'uuid_parse_bench',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'uuid_parse_bench',
+ ],
+ },
+}
diff --git a/src/test/modules/uuid_parse_bench/uuid_parse_bench--1.0.sql b/src/test/modules/uuid_parse_bench/uuid_parse_bench--1.0.sql
new file mode 100644
index 00000000000..d49371fa7c5
--- /dev/null
+++ b/src/test/modules/uuid_parse_bench/uuid_parse_bench--1.0.sql
@@ -0,0 +1,24 @@
+/* src/test/modules/uuid_parse_bench/uuid_parse_bench--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION uuid_parse_bench" to load this file. \quit
+
+--
+-- Time each parsing strategy against each input shape. Passing NULL for
+-- paths or shapes selects all of them.
+--
+-- Valid paths: scalar, simd, nosimd
+-- Valid shapes: canonical, bare32, braced_canonical, braced_bare32,
+-- dashed4, invalid_hex
+--
+CREATE FUNCTION uuid_parse_bench(nuuids int DEFAULT 100000,
+ nloops int DEFAULT 5,
+ paths text[] DEFAULT NULL,
+ shapes text[] DEFAULT NULL,
+ OUT path text,
+ OUT shape text,
+ OUT n_inputs int,
+ OUT best_ms float8,
+ OUT ns_per_parse float8)
+ RETURNS SETOF record
+ AS 'MODULE_PATHNAME' LANGUAGE C VOLATILE;
diff --git a/src/test/modules/uuid_parse_bench/uuid_parse_bench.c b/src/test/modules/uuid_parse_bench/uuid_parse_bench.c
new file mode 100644
index 00000000000..51e497fe98a
--- /dev/null
+++ b/src/test/modules/uuid_parse_bench/uuid_parse_bench.c
@@ -0,0 +1,641 @@
+/*--------------------------------------------------------------------------
+ *
+ * uuid_parse_bench.c
+ * Microbenchmark for the UUID input parsing strategies.
+ *
+ * This module times several ways of turning UUID text into a pg_uuid_t, so
+ * that the trade-offs can be argued with measurements rather than intuition.
+ * The paths it measures are:
+ *
+ * scalar the character-at-a-time loop using isxdigit() and strtoul()
+ * simd shape detection + compaction + hex_decode_safe()
+ * nosimd the same, but forced through the scalar hex decoder
+ *
+ * "simd" and "nosimd" differ only in which hex decoder they call, which is
+ * why this module carries its own copy of the scalar decoder: the real
+ * hex_decode_safe() picks its implementation at compile time, so a single
+ * backend binary can otherwise only measure one of the two.
+ *
+ * Copyright (c) 2007-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/uuid_parse_bench/uuid_parse_bench.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "catalog/pg_type_d.h"
+#include "common/pg_prng.h"
+#include "fmgr.h"
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "nodes/miscnodes.h"
+#include "portability/instr_time.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/tuplestore.h"
+#include "utils/uuid.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(uuid_parse_bench);
+
+/* number of hex digits in a UUID body */
+#define UUID_HEX_LEN (UUID_LEN * 2)
+/* length of the canonical 8x-4x-4x-4x-12x form */
+#define UUID_CANON_LEN (UUID_HEX_LEN + 4)
+
+/*
+ * The parsing strategies we time. Keep in sync with path_names[].
+ */
+typedef enum BenchPath
+{
+ PATH_SCALAR = 0,
+ PATH_SIMD,
+ PATH_NOSIMD,
+ NUM_PATHS,
+} BenchPath;
+
+static const char *const path_names[] = {
+ "scalar", "simd", "nosimd",
+};
+
+/*
+ * The input shapes we time. The fast path's advantage depends strongly on
+ * the shape, so this has to be an axis of the measurement rather than a
+ * single representative case. dashed4 and invalid_hex are the shapes that
+ * fall back to the scalar parser, and so measure the cost of the fallback.
+ */
+typedef enum BenchShape
+{
+ SHAPE_CANONICAL = 0, /* 8x-4x-4x-4x-12x, what uuid_out() emits */
+ SHAPE_BARE32, /* 32 contiguous hex digits */
+ SHAPE_BRACED_CANONICAL, /* {8x-4x-4x-4x-12x} */
+ SHAPE_BRACED_BARE32, /* {32 hex digits} */
+ SHAPE_DASHED4, /* dash after every group of 4: falls back */
+ SHAPE_INVALID_HEX, /* canonical but with a bad digit: falls back */
+ NUM_SHAPES,
+} BenchShape;
+
+static const char *const shape_names[] = {
+ "canonical", "bare32", "braced_canonical", "braced_bare32",
+ "dashed4", "invalid_hex",
+};
+
+/*
+ * Consume parse results so the compiler cannot delete the work we are timing.
+ */
+static volatile uint64 bench_sink = 0;
+
+/*
+ * Copy of string_to_uuid_scalar() from src/backend/utils/adt/uuid.c.
+ */
+static bool
+string_to_uuid_scalar(const char *source, pg_uuid_t *uuid)
+{
+ const char *src = source;
+ bool braces = false;
+ int i;
+
+ if (src[0] == '{')
+ {
+ src++;
+ braces = true;
+ }
+
+ for (i = 0; i < UUID_LEN; i++)
+ {
+ char str_buf[3];
+
+ if (src[0] == '\0' || src[1] == '\0')
+ return false;
+ memcpy(str_buf, src, 2);
+ if (!isxdigit((unsigned char) str_buf[0]) ||
+ !isxdigit((unsigned char) str_buf[1]))
+ return false;
+
+ str_buf[2] = '\0';
+ uuid->data[i] = (unsigned char) strtoul(str_buf, NULL, 16);
+ src += 2;
+ if (src[0] == '-' && (i % 2) == 1 && i < UUID_LEN - 1)
+ src++;
+ }
+
+ if (braces)
+ {
+ if (*src != '}')
+ return false;
+ src++;
+ }
+
+ return *src == '\0';
+}
+
+/*
+ * hexlookup[], get_hex() and hex_decode_safe_scalar() below are copies of the
+ * versions in src/backend/utils/adt/encode.c. They are static there, and in
+ * any case the exported hex_decode_safe() resolves to either the vectorized
+ * or the scalar implementation at compile time, so a backend built for a SIMD
+ * platform cannot otherwise measure what a non-SIMD platform would do.
+ *
+ * Being copies, these can drift from the originals. Re-check them against
+ * encode.c before quoting any figure.
+ */
+static const int8 bench_hexlookup[128] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
+
+static inline bool
+bench_get_hex(const char *cp, char *out)
+{
+ unsigned char c = (unsigned char) *cp;
+ int res = -1;
+
+ if (c < 127)
+ res = bench_hexlookup[c];
+
+ *out = (char) res;
+
+ return (res >= 0);
+}
+
+/*
+ * Copy of hex_decode_safe_scalar() in encode.c.
+ */
+static inline uint64
+bench_hex_decode_scalar(const char *src, size_t len, char *dst, bool *ok)
+{
+ const char *s,
+ *srcend;
+ char v1,
+ v2,
+ *p;
+
+ srcend = src + len;
+ s = src;
+ p = dst;
+ *ok = true;
+ while (s < srcend)
+ {
+ if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
+ {
+ s++;
+ continue;
+ }
+ if (!bench_get_hex(s, &v1))
+ {
+ *ok = false;
+ return p - dst;
+ }
+ s++;
+ if (s >= srcend)
+ {
+ *ok = false;
+ return p - dst;
+ }
+ if (!bench_get_hex(s, &v2))
+ {
+ *ok = false;
+ return p - dst;
+ }
+ s++;
+ *p++ = (v1 << 4) | v2;
+ }
+
+ return p - dst;
+}
+
+/* ----------------------------------------------------------------
+ * Input classification, shared by the fast paths
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * How a given input can be decoded. Note these are not BenchShape values:
+ * they say which fast path applies, not which shape the corpus was built in.
+ */
+#define FASTPATH_NONE 0 /* no fast path applies; hand off to scalar */
+#define FASTPATH_CONTIGUOUS 1 /* 32 hex digits, decode in place */
+#define FASTPATH_COMPACT 2 /* canonical, must have its dashes removed */
+
+/*
+ * Decide which fast path applies to source and, when one does, hand back the
+ * start of the body. This is exactly the work string_to_uuid() does before
+ * it decodes anything.
+ */
+static inline int
+classify_input(const char *source, const char **bodyp)
+{
+ const char *body = source;
+ size_t len = strlen(source);
+
+ /* Strip one optional surrounding brace pair */
+ if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
+ {
+ body = source + 1;
+ len -= 2;
+ }
+
+ *bodyp = body;
+
+ if (len == UUID_HEX_LEN)
+ return FASTPATH_CONTIGUOUS;
+ if (len == UUID_CANON_LEN && body[8] == '-' && body[13] == '-' &&
+ body[18] == '-' && body[23] == '-')
+ return FASTPATH_COMPACT;
+
+ return FASTPATH_NONE;
+}
+
+/*
+ * Compact the canonical form into 32 contiguous hex digits.
+ */
+static inline void
+compact_canonical(const char *body, char *hexbuf)
+{
+ memcpy(&hexbuf[0], &body[0], 8);
+ memcpy(&hexbuf[8], &body[9], 4);
+ memcpy(&hexbuf[12], &body[14], 4);
+ memcpy(&hexbuf[16], &body[19], 4);
+ memcpy(&hexbuf[20], &body[24], 12);
+}
+
+/*
+ * Copy of hex_decode_safe() with SIMD
+ */
+static bool
+hex_decode_safe_simd(const char *source, pg_uuid_t *uuid)
+{
+ const char *body;
+ const char *hexsrc;
+ char hexbuf[UUID_HEX_LEN];
+ uint64 written;
+ ErrorSaveContext esctx = {T_ErrorSaveContext};
+ int fastpath = classify_input(source, &body);
+
+ if (fastpath == FASTPATH_NONE)
+ return string_to_uuid_scalar(source, uuid);
+
+ if (fastpath == FASTPATH_COMPACT)
+ {
+ compact_canonical(body, hexbuf);
+ hexsrc = hexbuf;
+ }
+ else
+ hexsrc = body;
+
+ written = hex_decode_safe(hexsrc, UUID_HEX_LEN, (char *) uuid->data,
+ (Node *) &esctx);
+
+ if (esctx.error_occurred || written != UUID_LEN)
+ return string_to_uuid_scalar(source, uuid);
+
+ return true;
+}
+
+/*
+ * Simulating hex_decode_safe() with USE_NO_SIMD
+ */
+static bool
+hex_decode_safe_nosimd(const char *source, pg_uuid_t *uuid)
+{
+ const char *body;
+ const char *hexsrc;
+ char hexbuf[UUID_HEX_LEN];
+ uint64 written;
+ bool ok;
+ int fastpath = classify_input(source, &body);
+
+ if (fastpath == FASTPATH_NONE)
+ return string_to_uuid_scalar(source, uuid);
+
+ if (fastpath == FASTPATH_COMPACT)
+ {
+ compact_canonical(body, hexbuf);
+ hexsrc = hexbuf;
+ }
+ else
+ hexsrc = body;
+
+ written = bench_hex_decode_scalar(hexsrc, UUID_HEX_LEN,
+ (char *) uuid->data, &ok);
+
+ if (!ok || written != UUID_LEN)
+ return string_to_uuid_scalar(source, uuid);
+
+ return true;
+}
+
+/*
+ * Render one random UUID in the requested shape. The caller owns the buffer,
+ * which must hold at least 48 bytes.
+ */
+static void
+render_shape(pg_prng_state *state, BenchShape shape, char *buf)
+{
+ unsigned char b[UUID_LEN];
+ static const char hex[] = "0123456789abcdef";
+ char hexdigits[UUID_HEX_LEN];
+ char *p = buf;
+ int i;
+
+ for (i = 0; i < UUID_LEN; i++)
+ b[i] = (unsigned char) pg_prng_uint32(state);
+ for (i = 0; i < UUID_LEN; i++)
+ {
+ hexdigits[i * 2] = hex[b[i] >> 4];
+ hexdigits[i * 2 + 1] = hex[b[i] & 0x0f];
+ }
+
+ if (shape == SHAPE_BRACED_CANONICAL || shape == SHAPE_BRACED_BARE32)
+ *p++ = '{';
+
+ switch (shape)
+ {
+ case SHAPE_BARE32:
+ case SHAPE_BRACED_BARE32:
+ memcpy(p, hexdigits, UUID_HEX_LEN);
+ p += UUID_HEX_LEN;
+ break;
+
+ case SHAPE_DASHED4:
+ /* a dash after every group of 4: legal, but not the fast path */
+ for (i = 0; i < UUID_HEX_LEN; i++)
+ {
+ if (i > 0 && i % 4 == 0)
+ *p++ = '-';
+ *p++ = hexdigits[i];
+ }
+ break;
+
+ default:
+ /* canonical 8x-4x-4x-4x-12x, optionally with a bad digit */
+ for (i = 0; i < UUID_HEX_LEN; i++)
+ {
+ if (i == 8 || i == 12 || i == 16 || i == 20)
+ *p++ = '-';
+ *p++ = hexdigits[i];
+ }
+ /* corrupt one digit so the fast path has to hand off */
+ if (shape == SHAPE_INVALID_HEX)
+ buf[1] = 'z';
+ break;
+ }
+
+ if (shape == SHAPE_BRACED_CANONICAL || shape == SHAPE_BRACED_BARE32)
+ *p++ = '}';
+ *p = '\0';
+}
+
+/*
+ * Build nuuids input strings of the given shape in the current memory
+ * context. Everything is materialized before timing starts so that neither
+ * the PRNG nor the allocator shows up in the measurement.
+ */
+static char **
+build_corpus(BenchShape shape, int nuuids, uint64 seed)
+{
+ pg_prng_state state;
+ char **inputs = palloc(sizeof(char *) * nuuids);
+ int i;
+
+ pg_prng_seed(&state, seed);
+ for (i = 0; i < nuuids; i++)
+ {
+ char buf[48];
+
+ render_shape(&state, shape, buf);
+ inputs[i] = pstrdup(buf);
+ }
+
+ return inputs;
+}
+
+/* ----------------------------------------------------------------
+ * Timing
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * Run one path over one corpus nloops times and return the best wall time in
+ * milliseconds. The best of several runs is reported rather than the mean
+ * because it is the least contaminated by scheduling noise.
+ */
+/*
+ * The inner loop, instantiated once per path. Calling the parser directly
+ * rather than through parse_funcs[] matters: an indirect call would add the
+ * same few cycles to every path and so shrink exactly the differences this
+ * module exists to measure. uuid_in() calls string_to_uuid() directly, so
+ * direct calls are also the more faithful model.
+ */
+#define TIME_ONE_LOOP(fn) \
+ do { \
+ for (i = 0; i < nuuids; i++) \
+ { \
+ pg_uuid_t u; \
+ if (fn(inputs[i], &u)) \
+ local += u.data[0]; \
+ else \
+ local++; \
+ } \
+ } while (0)
+
+static double
+time_path(BenchPath path, char **inputs, int nuuids, int nloops)
+{
+ double best = -1.0;
+ int loop;
+
+ for (loop = 0; loop < nloops; loop++)
+ {
+ instr_time start,
+ stop;
+ uint64 local = 0;
+ double ms;
+ int i;
+
+ CHECK_FOR_INTERRUPTS();
+
+ INSTR_TIME_SET_CURRENT(start);
+ switch (path)
+ {
+ case PATH_SCALAR:
+ TIME_ONE_LOOP(string_to_uuid_scalar);
+ break;
+ case PATH_SIMD:
+ TIME_ONE_LOOP(hex_decode_safe_simd);
+ break;
+ case PATH_NOSIMD:
+ TIME_ONE_LOOP(hex_decode_safe_nosimd);
+ break;
+ case NUM_PATHS:
+ elog(ERROR, "unexpected path %d", (int) path);
+ break;
+ }
+ INSTR_TIME_SET_CURRENT(stop);
+ INSTR_TIME_SUBTRACT(stop, start);
+
+ bench_sink += local;
+
+ ms = INSTR_TIME_GET_DOUBLE(stop) * 1000.0;
+ if (best < 0.0 || ms < best)
+ best = ms;
+ }
+
+ return best;
+}
+
+/* ----------------------------------------------------------------
+ * SQL interface
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * Turn a text[] filter into a bool[] over names, or select everything when
+ * the array is NULL.
+ */
+static void
+parse_filter(ArrayType *arr, const char *const *names, int nnames,
+ bool *selected, const char *what)
+{
+ Datum *elems;
+ bool *nulls;
+ int nelems;
+ int i;
+
+ if (arr == NULL)
+ {
+ for (i = 0; i < nnames; i++)
+ selected[i] = true;
+ return;
+ }
+
+ for (i = 0; i < nnames; i++)
+ selected[i] = false;
+
+ deconstruct_array_builtin(arr, TEXTOID, &elems, &nulls, &nelems);
+ for (i = 0; i < nelems; i++)
+ {
+ char *name;
+ int j;
+
+ if (nulls[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("%s array must not contain nulls", what)));
+
+ name = TextDatumGetCString(elems[i]);
+ for (j = 0; j < nnames; j++)
+ {
+ if (strcmp(name, names[j]) == 0)
+ {
+ selected[j] = true;
+ break;
+ }
+ }
+ if (j == nnames)
+ {
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ for (j = 0; j < nnames; j++)
+ appendStringInfo(&buf, "%s%s", j > 0 ? ", " : "", names[j]);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized %s \"%s\"", what, name),
+ errhint("Valid values are: %s.", buf.data)));
+ }
+ }
+}
+
+/*
+ * uuid_parse_bench(nuuids, nloops, paths, shapes)
+ *
+ * Time every selected path against every selected shape and return the
+ * matrix.
+ */
+Datum
+uuid_parse_bench(PG_FUNCTION_ARGS)
+{
+ int nuuids = PG_GETARG_INT32(0);
+ int nloops = PG_GETARG_INT32(1);
+ ArrayType *patharr = PG_ARGISNULL(2) ? NULL : PG_GETARG_ARRAYTYPE_P(2);
+ ArrayType *shapearr = PG_ARGISNULL(3) ? NULL : PG_GETARG_ARRAYTYPE_P(3);
+ bool want_path[NUM_PATHS];
+ bool want_shape[NUM_SHAPES];
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ int shape;
+
+ if (nuuids <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("nuuids must be positive")));
+ if (nloops <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("nloops must be positive")));
+
+ parse_filter(patharr, path_names, NUM_PATHS, want_path, "path");
+ parse_filter(shapearr, shape_names, NUM_SHAPES, want_shape, "shape");
+
+ InitMaterializedSRF(fcinfo, 0);
+
+ for (shape = 0; shape < NUM_SHAPES; shape++)
+ {
+ MemoryContext corpusctx,
+ oldctx;
+ char **inputs;
+ int path;
+
+ if (!want_shape[shape])
+ continue;
+
+ /*
+ * Build each corpus in its own context so that the strings for one
+ * shape are freed before the next is built. With a large nuuids the
+ * whole matrix would otherwise be resident at once.
+ */
+ corpusctx = AllocSetContextCreate(CurrentMemoryContext,
+ "uuid_parse_bench corpus",
+ ALLOCSET_DEFAULT_SIZES);
+ oldctx = MemoryContextSwitchTo(corpusctx);
+ inputs = build_corpus(shape, nuuids, 42);
+ MemoryContextSwitchTo(oldctx);
+
+ for (path = 0; path < NUM_PATHS; path++)
+ {
+ Datum values[5];
+ bool nulls[5] = {0};
+ double best_ms;
+
+ if (!want_path[path])
+ continue;
+
+ best_ms = time_path(path, inputs, nuuids, nloops);
+
+ values[0] = CStringGetTextDatum(path_names[path]);
+ values[1] = CStringGetTextDatum(shape_names[shape]);
+ values[2] = Int32GetDatum(nuuids);
+ values[3] = Float8GetDatum(best_ms);
+ values[4] = Float8GetDatum(best_ms * 1000000.0 / nuuids);
+
+ tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+ values, nulls);
+ }
+
+ MemoryContextDelete(corpusctx);
+ }
+
+ return (Datum) 0;
+}
diff --git a/src/test/modules/uuid_parse_bench/uuid_parse_bench.control b/src/test/modules/uuid_parse_bench/uuid_parse_bench.control
new file mode 100644
index 00000000000..60e75d3df2d
--- /dev/null
+++ b/src/test/modules/uuid_parse_bench/uuid_parse_bench.control
@@ -0,0 +1,5 @@
+# uuid_parse_bench extension
+comment = 'Microbenchmark for UUID input parsing'
+default_version = '1.0'
+module_pathname = '$libdir/uuid_parse_bench'
+relocatable = true
--
2.55.0
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-06 03:40 Chao Li <li.evan.chao@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
1 sibling, 1 reply; 27+ messages in thread
From: Chao Li @ 2026-08-06 03:40 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
> On Aug 6, 2026, at 08:19, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <tristan.yim@gmail.com> wrote:
>>
>> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>
>>> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>>>>
>>>> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>
>>>>> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>>>>>>
>>>>>> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>>>
>>>>>>> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>>>>>
>>>>>>>>> Hi all,
>>>>>>>>>
>>>>>>>>> I'd like to propose the $subject.
>>>>>>>>>
>>>>>>>>> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
>>>>>>>>> a run of hex digits is now fast. The attached patch reuses
>>>>>>>>> hex_decode_safe() in the UUID input function to speed up parsing.
>>>>>>>>>
>>>>>>>>> We accept several textual forms of a UUID[1]. The fast path handles
>>>>>>>>> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
>>>>>>>>> (where "nx" means n hex digits), and either of those wrapped in
>>>>>>>>> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
>>>>>>>>>
>>>>>>>>> I've benchmarked the parse speed using the following query:
>>>>>>>>>
>>>>>>>>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
>>>>>>>>> generate_series(1, 1000000);
>>>>>>>>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
>>>>>>>>>
>>>>>>>>> I compared the execution time of the second query, which measures
>>>>>>>>> uuid_in() alone, with/without SIMD optimization. Here are results (the
>>>>>>>>> median of 5 runs):
>>>>>>>>>
>>>>>>>>> HEAD: 208.879 ms
>>>>>>>>> Patched: 40.983 ms
>>>>>>>>>
>>>>>>>>> The improvements look promising to me. But in a realistic pipeline the
>>>>>>>>> parse is a small fraction of the work, so end-to-end gains could be
>>>>>>>>> much smaller.
>>>>>>>>>
>>>>>>>>> Feedback is very welcome.
>>>>>>>>>
>>>>>>>> I may be missing something, but I wonder whether the fast path is relying on
>>>>>>>> slightly different input semantics from the existing UUID parser.
>>>>>>>>
>>>>>>>> In particular, hex_decode_safe() is not a strict “32 hex characters only”
>>>>>>>> decoder. It skips whitespace, which is fine for its existing callers, but I
>>>>>>>> don’t think UUID input should treat whitespace inside the UUID body as
>>>>>>>> ignorable.
>>>>>>>
>>>>>>> Good catch! hex_decode_safe() skips whitespaces so the patch accepts
>>>>>>> the following UUID value, which is bad:
>>>>>>>
>>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
>>>>>>> uuid
>>>>>>> --------------------------------------
>>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00
>>>>>>> (1 row)
>>>>>>>
>>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast path
>>>>>>>> cannot verify that exactly UUID_LEN bytes were produced.
>>>>>>>
>>>>>>> IIUC hex_decode_safe() does return the output length in bytes. So I
>>>>>>> think we can fallback to the scalar UUID parser if
>>>>>>> esctx.error_occurred is true or if the returned value is not 16.
>>>>>>>
>>>>>>
>>>>>> You’re right, I misread that part. Checking both esctx.error_occurred and
>>>>>> the returned length sounds good to me.
>>>>>>
>>>>>>>>
>>>>>>>> So I think it would be safer either to pre-validate that the 32 source
>>>>>>>> characters are all hex digits before calling hex_decode_safe(), or to use a
>>>>>>>> UUID-specific strict hex decoder for this path. After that, a comment
>>>>>>>> explaining why hex_decode_safe() is safe here would make the invariant much
>>>>>>>> clearer.
>>>>>>>
>>>>>>> IIUC hex_decode_simd_helper() accepts only hex digits so we could
>>>>>>> re-use it for UUID parsing. Let me check if the above idea of using
>>>>>>> the return value works for us first.
>>>>>>>
>>>>>>
>>>>>> That sounds reasonable. My main concern was to keep the fast path’s accepted
>>>>>> input set identical to the scalar UUID parser. Falling back when the decoded
>>>>>> length is not UUID_LEN, together with regression tests for whitespace cases,
>>>>>> should address that.
>>>>>>
>>>>>>>>
>>>>>>>> Could you also add a few regression tests for invalid inputs that contain
>>>>>>>> whitespace inside otherwise fast-path-looking UUID strings? For example:
>>>>>>>>
>>>>>>>> ---------------------------------------------------------------
>>>>>>>>
>>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
>>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
>>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
>>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
>>>>>>>> ---------------------------------------------------------------
>>>>>>>>
>>>>>>>> These should continue to be rejected in the same way as the scalar parser.
>>>>>>>> Regards,
>>>>>>>
>>>>>>> Agreed.
>>>>>>>
>>>>>
>>>>> I've attached the updated patch.
>>>>>
>>>>> Regards,
>>>>>
>>>>> --
>>>>> Masahiko Sawada
>>>>> Amazon Web Services: https://aws.amazon.com
>>>>
>>>> I noticed a few typos in the comments:
>>>>
>>>> src/backend/utils/adt/uuid.c
>>>> line 56: “scalar implmentation” -> “scalar implementation”
>>>> line 109: “swalled” -> “swallowed”
>>>> line 110: “kepping” -> “keeping”
>>>> line 118: “grammer” -> “grammar”
>>>> line 119: “whitespaces” -> “whitespace”
>>>>
>>>> Could you fix them ?
>>>
>>> Oops, I fixed them and rechecked other places.
>>>
>>> I've attached the updated patch.
>>>
>>> Regards,
>>>
>>> --
>>> Masahiko Sawada
>>> Amazon Web Services: https://aws.amazon.com
>>
>> The code looks good to me now. I only noticed one small typo in the
>> commit trailer: Reviwed-by should be Reviewed-by.
>>
>> Otherwise, it looks good. Thank you for fixing these issues.
>>
>
> After spending more time on this patch, I find out two things:
>
> 1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
> But including port/simd.h seems wrong as it doesn't use any SIMD
> support functions.
>
> 2. hex_decode_safe() is faster than the current UUID parse
> (isxdigit()+strtoul() approach) even without SIMD. I've created a
> small benchmark test tool (attached as 0002 patch, not intended to be
> pushed into the core), and measures UUID parsing performance of three
> approaches: 'scalar' is the current string_to_uuid() that uses
> isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
> 'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
> UUIDs. Here are results:
>
> =# select path, shape, n_inputs, best_ms::numeric(10,3) from
> uuid_parse_bench(100000, 5);
> path | shape | n_inputs | best_ms
> --------+------------------+----------+---------
> scalar | canonical | 100000 | 22.661
> simd | canonical | 100000 | 1.400
> nosimd | canonical | 100000 | 1.652
> scalar | bare32 | 100000 | 15.932
> simd | bare32 | 100000 | 0.471
> nosimd | bare32 | 100000 | 1.110
> scalar | braced_canonical | 100000 | 17.330
> simd | braced_canonical | 100000 | 1.088
> nosimd | braced_canonical | 100000 | 1.314
> scalar | braced_bare32 | 100000 | 15.942
> simd | braced_bare32 | 100000 | 0.488
> nosimd | braced_bare32 | 100000 | 1.141
> scalar | dashed4 | 100000 | 16.185
> simd | dashed4 | 100000 | 16.493
> nosimd | dashed4 | 100000 | 16.403
> scalar | invalid_hex | 100000 | 0.199
> simd | invalid_hex | 100000 | 1.150
> nosimd | invalid_hex | 100000 | 0.385
> (18 rows)
>
> Each of shape means:
> - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
> - 'bare32': 32 contiguous hex digits
> - 'braced_canonical': {8x-4x-4x-4x-12x}
> - 'braced_bare32': {32 hex digits}
> - 'dashed4': dash after every group of 4
> - 'invalid_hdx': canonical but with a invalid digit
>
> 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
> mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
> and 'nosimd' fall back to the 'scalar' case. According to these
> results, my conclusion is that we can use hex_decode_safe() for
> canonical forms and 32 contiguous hex forms anyway, and let
> hex_decode_safe() choose whether to use SIMD. We would win in either
> case. We still use the current scalar approach for uncommon UUID forms
> and error reporting purposes.
>
> Regards,
>
> --
> Masahiko Sawada
> Amazon Web Services: https://aws.amazon.com
> <v4-0001-Optimize-UUID-parse-using-SIMD.patch><v4-0002-uuid_parse_bench-module.patch>
A few comments on v4.
1 - 0001
```
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+ const char *body = source;
+ size_t len = strlen(source);
```
I think it would be better to avoid strlen(). The old code processes at most UUID_LEN (16) byte pairs, so it does not need to scan arbitrarily far on malformed input. So, maybe we could use something like strnlen(source, 39) instead.
2 - 0002 - uuid_parse_bench/Makefile
```
+REGRESS = uuid_parse_bench
```
When I tried to run the test, I got an error:
```
# +++ regress check in src/test/modules/uuid_parse_bench +++
# initializing database system by copying initdb template
# using temp instance on port 52544 with PID 56970
/bin/sh: /Users/chaol/Documents/code/postgresql/src/test/modules/uuid_parse_bench/sql/uuid_parse_bench.sql: No such file or directory
diff: /Users/chaol/Documents/code/postgresql/src/test/modules/uuid_parse_bench/expected/uuid_parse_bench.out: No such file or directory
```
Not sure if you missed to check in uuid_parse_bench.sql and uuid_parse_bench.out.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-06 16:09 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Chao Li <li.evan.chao@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Masahiko Sawada @ 2026-08-06 16:09 UTC (permalink / raw)
To: Chao Li <li.evan.chao@gmail.com>; +Cc: Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
On Wed, Aug 5, 2026 at 8:40 PM Chao Li <li.evan.chao@gmail.com> wrote:
>
>
>
> > On Aug 6, 2026, at 08:19, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >
> > On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <tristan.yim@gmail.com> wrote:
> >>
> >> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>
> >>> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >>>>
> >>>> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>>>
> >>>>> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >>>>>>
> >>>>>> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>>>>>
> >>>>>>> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >>>>>>>>
> >>>>>>>>
> >>>>>>>>
> >>>>>>>> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>>>>>>>
> >>>>>>>>> Hi all,
> >>>>>>>>>
> >>>>>>>>> I'd like to propose the $subject.
> >>>>>>>>>
> >>>>>>>>> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> >>>>>>>>> a run of hex digits is now fast. The attached patch reuses
> >>>>>>>>> hex_decode_safe() in the UUID input function to speed up parsing.
> >>>>>>>>>
> >>>>>>>>> We accept several textual forms of a UUID[1]. The fast path handles
> >>>>>>>>> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> >>>>>>>>> (where "nx" means n hex digits), and either of those wrapped in
> >>>>>>>>> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> >>>>>>>>>
> >>>>>>>>> I've benchmarked the parse speed using the following query:
> >>>>>>>>>
> >>>>>>>>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> >>>>>>>>> generate_series(1, 1000000);
> >>>>>>>>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> >>>>>>>>>
> >>>>>>>>> I compared the execution time of the second query, which measures
> >>>>>>>>> uuid_in() alone, with/without SIMD optimization. Here are results (the
> >>>>>>>>> median of 5 runs):
> >>>>>>>>>
> >>>>>>>>> HEAD: 208.879 ms
> >>>>>>>>> Patched: 40.983 ms
> >>>>>>>>>
> >>>>>>>>> The improvements look promising to me. But in a realistic pipeline the
> >>>>>>>>> parse is a small fraction of the work, so end-to-end gains could be
> >>>>>>>>> much smaller.
> >>>>>>>>>
> >>>>>>>>> Feedback is very welcome.
> >>>>>>>>>
> >>>>>>>> I may be missing something, but I wonder whether the fast path is relying on
> >>>>>>>> slightly different input semantics from the existing UUID parser.
> >>>>>>>>
> >>>>>>>> In particular, hex_decode_safe() is not a strict “32 hex characters only”
> >>>>>>>> decoder. It skips whitespace, which is fine for its existing callers, but I
> >>>>>>>> don’t think UUID input should treat whitespace inside the UUID body as
> >>>>>>>> ignorable.
> >>>>>>>
> >>>>>>> Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> >>>>>>> the following UUID value, which is bad:
> >>>>>>>
> >>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> >>>>>>> uuid
> >>>>>>> --------------------------------------
> >>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00
> >>>>>>> (1 row)
> >>>>>>>
> >>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast path
> >>>>>>>> cannot verify that exactly UUID_LEN bytes were produced.
> >>>>>>>
> >>>>>>> IIUC hex_decode_safe() does return the output length in bytes. So I
> >>>>>>> think we can fallback to the scalar UUID parser if
> >>>>>>> esctx.error_occurred is true or if the returned value is not 16.
> >>>>>>>
> >>>>>>
> >>>>>> You’re right, I misread that part. Checking both esctx.error_occurred and
> >>>>>> the returned length sounds good to me.
> >>>>>>
> >>>>>>>>
> >>>>>>>> So I think it would be safer either to pre-validate that the 32 source
> >>>>>>>> characters are all hex digits before calling hex_decode_safe(), or to use a
> >>>>>>>> UUID-specific strict hex decoder for this path. After that, a comment
> >>>>>>>> explaining why hex_decode_safe() is safe here would make the invariant much
> >>>>>>>> clearer.
> >>>>>>>
> >>>>>>> IIUC hex_decode_simd_helper() accepts only hex digits so we could
> >>>>>>> re-use it for UUID parsing. Let me check if the above idea of using
> >>>>>>> the return value works for us first.
> >>>>>>>
> >>>>>>
> >>>>>> That sounds reasonable. My main concern was to keep the fast path’s accepted
> >>>>>> input set identical to the scalar UUID parser. Falling back when the decoded
> >>>>>> length is not UUID_LEN, together with regression tests for whitespace cases,
> >>>>>> should address that.
> >>>>>>
> >>>>>>>>
> >>>>>>>> Could you also add a few regression tests for invalid inputs that contain
> >>>>>>>> whitespace inside otherwise fast-path-looking UUID strings? For example:
> >>>>>>>>
> >>>>>>>> ---------------------------------------------------------------
> >>>>>>>>
> >>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> >>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> >>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> >>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> >>>>>>>> ---------------------------------------------------------------
> >>>>>>>>
> >>>>>>>> These should continue to be rejected in the same way as the scalar parser.
> >>>>>>>> Regards,
> >>>>>>>
> >>>>>>> Agreed.
> >>>>>>>
> >>>>>
> >>>>> I've attached the updated patch.
> >>>>>
> >>>>> Regards,
> >>>>>
> >>>>> --
> >>>>> Masahiko Sawada
> >>>>> Amazon Web Services: https://aws.amazon.com
> >>>>
> >>>> I noticed a few typos in the comments:
> >>>>
> >>>> src/backend/utils/adt/uuid.c
> >>>> line 56: “scalar implmentation” -> “scalar implementation”
> >>>> line 109: “swalled” -> “swallowed”
> >>>> line 110: “kepping” -> “keeping”
> >>>> line 118: “grammer” -> “grammar”
> >>>> line 119: “whitespaces” -> “whitespace”
> >>>>
> >>>> Could you fix them ?
> >>>
> >>> Oops, I fixed them and rechecked other places.
> >>>
> >>> I've attached the updated patch.
> >>>
> >>> Regards,
> >>>
> >>> --
> >>> Masahiko Sawada
> >>> Amazon Web Services: https://aws.amazon.com
> >>
> >> The code looks good to me now. I only noticed one small typo in the
> >> commit trailer: Reviwed-by should be Reviewed-by.
> >>
> >> Otherwise, it looks good. Thank you for fixing these issues.
> >>
> >
> > After spending more time on this patch, I find out two things:
> >
> > 1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
> > But including port/simd.h seems wrong as it doesn't use any SIMD
> > support functions.
> >
> > 2. hex_decode_safe() is faster than the current UUID parse
> > (isxdigit()+strtoul() approach) even without SIMD. I've created a
> > small benchmark test tool (attached as 0002 patch, not intended to be
> > pushed into the core), and measures UUID parsing performance of three
> > approaches: 'scalar' is the current string_to_uuid() that uses
> > isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
> > 'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
> > UUIDs. Here are results:
> >
> > =# select path, shape, n_inputs, best_ms::numeric(10,3) from
> > uuid_parse_bench(100000, 5);
> > path | shape | n_inputs | best_ms
> > --------+------------------+----------+---------
> > scalar | canonical | 100000 | 22.661
> > simd | canonical | 100000 | 1.400
> > nosimd | canonical | 100000 | 1.652
> > scalar | bare32 | 100000 | 15.932
> > simd | bare32 | 100000 | 0.471
> > nosimd | bare32 | 100000 | 1.110
> > scalar | braced_canonical | 100000 | 17.330
> > simd | braced_canonical | 100000 | 1.088
> > nosimd | braced_canonical | 100000 | 1.314
> > scalar | braced_bare32 | 100000 | 15.942
> > simd | braced_bare32 | 100000 | 0.488
> > nosimd | braced_bare32 | 100000 | 1.141
> > scalar | dashed4 | 100000 | 16.185
> > simd | dashed4 | 100000 | 16.493
> > nosimd | dashed4 | 100000 | 16.403
> > scalar | invalid_hex | 100000 | 0.199
> > simd | invalid_hex | 100000 | 1.150
> > nosimd | invalid_hex | 100000 | 0.385
> > (18 rows)
> >
> > Each of shape means:
> > - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
> > - 'bare32': 32 contiguous hex digits
> > - 'braced_canonical': {8x-4x-4x-4x-12x}
> > - 'braced_bare32': {32 hex digits}
> > - 'dashed4': dash after every group of 4
> > - 'invalid_hdx': canonical but with a invalid digit
> >
> > 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
> > mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
> > and 'nosimd' fall back to the 'scalar' case. According to these
> > results, my conclusion is that we can use hex_decode_safe() for
> > canonical forms and 32 contiguous hex forms anyway, and let
> > hex_decode_safe() choose whether to use SIMD. We would win in either
> > case. We still use the current scalar approach for uncommon UUID forms
> > and error reporting purposes.
> >
> > Regards,
> >
> > --
> > Masahiko Sawada
> > Amazon Web Services: https://aws.amazon.com
> > <v4-0001-Optimize-UUID-parse-using-SIMD.patch><v4-0002-uuid_parse_bench-module.patch>
>
> A few comments on v4.
>
> 1 - 0001
> ```
> +static void
> +string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
> +{
> + const char *body = source;
> + size_t len = strlen(source);
> ```
>
> I think it would be better to avoid strlen(). The old code processes at most UUID_LEN (16) byte pairs, so it does not need to scan arbitrarily far on malformed input. So, maybe we could use something like strnlen(source, 39) instead.
While strnlen(source, 39) works there, 39 is a magic number and it's
tied to the current format check logic. What is the benefit of using
strnlen(source, 39) instead? I'm not sure it warrants having the magic
number.
>
> 2 - 0002 - uuid_parse_bench/Makefile
> ```
> +REGRESS = uuid_parse_bench
> ```
>
> When I tried to run the test, I got an error:
> ```
> # +++ regress check in src/test/modules/uuid_parse_bench +++
> # initializing database system by copying initdb template
> # using temp instance on port 52544 with PID 56970
> /bin/sh: /Users/chaol/Documents/code/postgresql/src/test/modules/uuid_parse_bench/sql/uuid_parse_bench.sql: No such file or directory
> diff: /Users/chaol/Documents/code/postgresql/src/test/modules/uuid_parse_bench/expected/uuid_parse_bench.out: No such file or directory
> ```
>
> Not sure if you missed to check in uuid_parse_bench.sql and uuid_parse_bench.out.
Oops, I should have removed that line. The extension creates the sole
uuid_parse_bench() SQL function, so you can simply execute
uuid_parse_bench() to measure the performance. Please note that the
extension is not intended to push to the core.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-06 21:42 Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
1 sibling, 0 replies; 27+ messages in thread
From: Bharath Rupireddy @ 2026-08-06 21:42 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
Hi,
On Wed, Aug 5, 2026 at 5:20 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> After spending more time on this patch, I find out two things:
>
> 1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
> But including port/simd.h seems wrong as it doesn't use any SIMD
> support functions.
In v3, uuid.c doesn't include port/simd.h, so USE_NO_SIMD isn't
defined there. That makes the USE_NO_SIMD check in string_to_uuid()
confusing, since it tests a macro that isn't visible in this file. The
check reads as though it sends no-SIMD builds to the scalar parser,
but because the macro is undefined, string_to_uuid() always runs the
fast path, even on a no-SIMD build. That does no harm because the fast
path still falls back to the scalar parser for uncommon shapes and
errors, and hex_decode_safe() picks its own scalar path when SIMD is
off, which is already faster than isxdigit()+strtoul(). So the gate
wasn't buying anything, and v4 drops it, which is the right call.
> 2. hex_decode_safe() is faster than the current UUID parse
> (isxdigit()+strtoul() approach) even without SIMD. I've created a
> small benchmark test tool (attached as 0002 patch, not intended to be
> pushed into the core), and measures UUID parsing performance of three
> approaches: 'scalar' is the current string_to_uuid() that uses
> isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
> 'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
> UUIDs. Here are results:
>
> =# select path, shape, n_inputs, best_ms::numeric(10,3) from
> uuid_parse_bench(100000, 5);
> path | shape | n_inputs | best_ms
> --------+------------------+----------+---------
> scalar | canonical | 100000 | 22.661
> simd | canonical | 100000 | 1.400
> nosimd | canonical | 100000 | 1.652
> scalar | bare32 | 100000 | 15.932
> simd | bare32 | 100000 | 0.471
> nosimd | bare32 | 100000 | 1.110
> scalar | braced_canonical | 100000 | 17.330
> simd | braced_canonical | 100000 | 1.088
> nosimd | braced_canonical | 100000 | 1.314
> scalar | braced_bare32 | 100000 | 15.942
> simd | braced_bare32 | 100000 | 0.488
> nosimd | braced_bare32 | 100000 | 1.141
> scalar | dashed4 | 100000 | 16.185
> simd | dashed4 | 100000 | 16.493
> nosimd | dashed4 | 100000 | 16.403
> scalar | invalid_hex | 100000 | 0.199
> simd | invalid_hex | 100000 | 1.150
> nosimd | invalid_hex | 100000 | 0.385
> (18 rows)
>
> Each of shape means:
> - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
> - 'bare32': 32 contiguous hex digits
> - 'braced_canonical': {8x-4x-4x-4x-12x}
> - 'braced_bare32': {32 hex digits}
> - 'dashed4': dash after every group of 4
> - 'invalid_hdx': canonical but with a invalid digit
>
> 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
> mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
> and 'nosimd' fall back to the 'scalar' case. According to these
> results, my conclusion is that we can use hex_decode_safe() for
> canonical forms and 32 contiguous hex forms anyway, and let
> hex_decode_safe() choose whether to use SIMD. We would win in either
> case. We still use the current scalar approach for uncommon UUID forms
> and error reporting purposes.
I ran the benchmark locally and see similar numbers. Both paths in
hex_decode_safe() beat the current isxdigit()+strtoul() approach. The
SIMD path is roughly 20-40x faster on the common shapes, and even the
no-SIMD path is about 15x faster.
path | shape | n_inputs | best_ms
--------+------------------+----------+---------
scalar | canonical | 100000 | 31.820
simd | canonical | 100000 | 1.393
nosimd | canonical | 100000 | 2.136
scalar | bare32 | 100000 | 29.585
simd | bare32 | 100000 | 0.699
nosimd | bare32 | 100000 | 2.002
scalar | braced_canonical | 100000 | 31.906
simd | braced_canonical | 100000 | 1.412
nosimd | braced_canonical | 100000 | 2.143
scalar | braced_bare32 | 100000 | 29.170
simd | braced_bare32 | 100000 | 0.751
nosimd | braced_bare32 | 100000 | 2.031
scalar | dashed4 | 100000 | 30.424
simd | dashed4 | 100000 | 30.869
nosimd | dashed4 | 100000 | 30.900
scalar | invalid_hex | 100000 | 0.320
simd | invalid_hex | 100000 | 1.752
nosimd | invalid_hex | 100000 | 0.708
(18 rows)
The attached v4-0001 patch looks good to me. One comment though. Can
we add a positive test case that hits string_to_uuid_scalar and does
not error out?
--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-07 03:07 Chao Li <li.evan.chao@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Chao Li @ 2026-08-07 03:07 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
> On Aug 7, 2026, at 00:09, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> On Wed, Aug 5, 2026 at 8:40 PM Chao Li <li.evan.chao@gmail.com> wrote:
>>
>>
>>
>>> On Aug 6, 2026, at 08:19, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>
>>> On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <tristan.yim@gmail.com> wrote:
>>>>
>>>> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>
>>>>> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>>>>>>
>>>>>> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>>>
>>>>>>> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>>>>>>>>
>>>>>>>> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>>>>>
>>>>>>>>> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>>>>>>>
>>>>>>>>>>> Hi all,
>>>>>>>>>>>
>>>>>>>>>>> I'd like to propose the $subject.
>>>>>>>>>>>
>>>>>>>>>>> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
>>>>>>>>>>> a run of hex digits is now fast. The attached patch reuses
>>>>>>>>>>> hex_decode_safe() in the UUID input function to speed up parsing.
>>>>>>>>>>>
>>>>>>>>>>> We accept several textual forms of a UUID[1]. The fast path handles
>>>>>>>>>>> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
>>>>>>>>>>> (where "nx" means n hex digits), and either of those wrapped in
>>>>>>>>>>> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
>>>>>>>>>>>
>>>>>>>>>>> I've benchmarked the parse speed using the following query:
>>>>>>>>>>>
>>>>>>>>>>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
>>>>>>>>>>> generate_series(1, 1000000);
>>>>>>>>>>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
>>>>>>>>>>>
>>>>>>>>>>> I compared the execution time of the second query, which measures
>>>>>>>>>>> uuid_in() alone, with/without SIMD optimization. Here are results (the
>>>>>>>>>>> median of 5 runs):
>>>>>>>>>>>
>>>>>>>>>>> HEAD: 208.879 ms
>>>>>>>>>>> Patched: 40.983 ms
>>>>>>>>>>>
>>>>>>>>>>> The improvements look promising to me. But in a realistic pipeline the
>>>>>>>>>>> parse is a small fraction of the work, so end-to-end gains could be
>>>>>>>>>>> much smaller.
>>>>>>>>>>>
>>>>>>>>>>> Feedback is very welcome.
>>>>>>>>>>>
>>>>>>>>>> I may be missing something, but I wonder whether the fast path is relying on
>>>>>>>>>> slightly different input semantics from the existing UUID parser.
>>>>>>>>>>
>>>>>>>>>> In particular, hex_decode_safe() is not a strict “32 hex characters only”
>>>>>>>>>> decoder. It skips whitespace, which is fine for its existing callers, but I
>>>>>>>>>> don’t think UUID input should treat whitespace inside the UUID body as
>>>>>>>>>> ignorable.
>>>>>>>>>
>>>>>>>>> Good catch! hex_decode_safe() skips whitespaces so the patch accepts
>>>>>>>>> the following UUID value, which is bad:
>>>>>>>>>
>>>>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
>>>>>>>>> uuid
>>>>>>>>> --------------------------------------
>>>>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00
>>>>>>>>> (1 row)
>>>>>>>>>
>>>>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast path
>>>>>>>>>> cannot verify that exactly UUID_LEN bytes were produced.
>>>>>>>>>
>>>>>>>>> IIUC hex_decode_safe() does return the output length in bytes. So I
>>>>>>>>> think we can fallback to the scalar UUID parser if
>>>>>>>>> esctx.error_occurred is true or if the returned value is not 16.
>>>>>>>>>
>>>>>>>>
>>>>>>>> You’re right, I misread that part. Checking both esctx.error_occurred and
>>>>>>>> the returned length sounds good to me.
>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> So I think it would be safer either to pre-validate that the 32 source
>>>>>>>>>> characters are all hex digits before calling hex_decode_safe(), or to use a
>>>>>>>>>> UUID-specific strict hex decoder for this path. After that, a comment
>>>>>>>>>> explaining why hex_decode_safe() is safe here would make the invariant much
>>>>>>>>>> clearer.
>>>>>>>>>
>>>>>>>>> IIUC hex_decode_simd_helper() accepts only hex digits so we could
>>>>>>>>> re-use it for UUID parsing. Let me check if the above idea of using
>>>>>>>>> the return value works for us first.
>>>>>>>>>
>>>>>>>>
>>>>>>>> That sounds reasonable. My main concern was to keep the fast path’s accepted
>>>>>>>> input set identical to the scalar UUID parser. Falling back when the decoded
>>>>>>>> length is not UUID_LEN, together with regression tests for whitespace cases,
>>>>>>>> should address that.
>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Could you also add a few regression tests for invalid inputs that contain
>>>>>>>>>> whitespace inside otherwise fast-path-looking UUID strings? For example:
>>>>>>>>>>
>>>>>>>>>> ---------------------------------------------------------------
>>>>>>>>>>
>>>>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
>>>>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
>>>>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
>>>>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
>>>>>>>>>> ---------------------------------------------------------------
>>>>>>>>>>
>>>>>>>>>> These should continue to be rejected in the same way as the scalar parser.
>>>>>>>>>> Regards,
>>>>>>>>>
>>>>>>>>> Agreed.
>>>>>>>>>
>>>>>>>
>>>>>>> I've attached the updated patch.
>>>>>>>
>>>>>>> Regards,
>>>>>>>
>>>>>>> --
>>>>>>> Masahiko Sawada
>>>>>>> Amazon Web Services: https://aws.amazon.com
>>>>>>
>>>>>> I noticed a few typos in the comments:
>>>>>>
>>>>>> src/backend/utils/adt/uuid.c
>>>>>> line 56: “scalar implmentation” -> “scalar implementation”
>>>>>> line 109: “swalled” -> “swallowed”
>>>>>> line 110: “kepping” -> “keeping”
>>>>>> line 118: “grammer” -> “grammar”
>>>>>> line 119: “whitespaces” -> “whitespace”
>>>>>>
>>>>>> Could you fix them ?
>>>>>
>>>>> Oops, I fixed them and rechecked other places.
>>>>>
>>>>> I've attached the updated patch.
>>>>>
>>>>> Regards,
>>>>>
>>>>> --
>>>>> Masahiko Sawada
>>>>> Amazon Web Services: https://aws.amazon.com
>>>>
>>>> The code looks good to me now. I only noticed one small typo in the
>>>> commit trailer: Reviwed-by should be Reviewed-by.
>>>>
>>>> Otherwise, it looks good. Thank you for fixing these issues.
>>>>
>>>
>>> After spending more time on this patch, I find out two things:
>>>
>>> 1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
>>> But including port/simd.h seems wrong as it doesn't use any SIMD
>>> support functions.
>>>
>>> 2. hex_decode_safe() is faster than the current UUID parse
>>> (isxdigit()+strtoul() approach) even without SIMD. I've created a
>>> small benchmark test tool (attached as 0002 patch, not intended to be
>>> pushed into the core), and measures UUID parsing performance of three
>>> approaches: 'scalar' is the current string_to_uuid() that uses
>>> isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
>>> 'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
>>> UUIDs. Here are results:
>>>
>>> =# select path, shape, n_inputs, best_ms::numeric(10,3) from
>>> uuid_parse_bench(100000, 5);
>>> path | shape | n_inputs | best_ms
>>> --------+------------------+----------+---------
>>> scalar | canonical | 100000 | 22.661
>>> simd | canonical | 100000 | 1.400
>>> nosimd | canonical | 100000 | 1.652
>>> scalar | bare32 | 100000 | 15.932
>>> simd | bare32 | 100000 | 0.471
>>> nosimd | bare32 | 100000 | 1.110
>>> scalar | braced_canonical | 100000 | 17.330
>>> simd | braced_canonical | 100000 | 1.088
>>> nosimd | braced_canonical | 100000 | 1.314
>>> scalar | braced_bare32 | 100000 | 15.942
>>> simd | braced_bare32 | 100000 | 0.488
>>> nosimd | braced_bare32 | 100000 | 1.141
>>> scalar | dashed4 | 100000 | 16.185
>>> simd | dashed4 | 100000 | 16.493
>>> nosimd | dashed4 | 100000 | 16.403
>>> scalar | invalid_hex | 100000 | 0.199
>>> simd | invalid_hex | 100000 | 1.150
>>> nosimd | invalid_hex | 100000 | 0.385
>>> (18 rows)
>>>
>>> Each of shape means:
>>> - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
>>> - 'bare32': 32 contiguous hex digits
>>> - 'braced_canonical': {8x-4x-4x-4x-12x}
>>> - 'braced_bare32': {32 hex digits}
>>> - 'dashed4': dash after every group of 4
>>> - 'invalid_hdx': canonical but with a invalid digit
>>>
>>> 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
>>> mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
>>> and 'nosimd' fall back to the 'scalar' case. According to these
>>> results, my conclusion is that we can use hex_decode_safe() for
>>> canonical forms and 32 contiguous hex forms anyway, and let
>>> hex_decode_safe() choose whether to use SIMD. We would win in either
>>> case. We still use the current scalar approach for uncommon UUID forms
>>> and error reporting purposes.
>>>
>>> Regards,
>>>
>>> --
>>> Masahiko Sawada
>>> Amazon Web Services: https://aws.amazon.com
>>> <v4-0001-Optimize-UUID-parse-using-SIMD.patch><v4-0002-uuid_parse_bench-module.patch>
>>
>> A few comments on v4.
>>
>> 1 - 0001
>> ```
>> +static void
>> +string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
>> +{
>> + const char *body = source;
>> + size_t len = strlen(source);
>> ```
>>
>> I think it would be better to avoid strlen(). The old code processes at most UUID_LEN (16) byte pairs, so it does not need to scan arbitrarily far on malformed input. So, maybe we could use something like strnlen(source, 39) instead.
>
> While strnlen(source, 39) works there, 39 is a magic number and it's
> tied to the current format check logic. What is the benefit of using
> strnlen(source, 39) instead? I'm not sure it warrants having the magic
> number.
It doesn't have to be exactly 39; 1024 (long enough) would also work, or perhaps something based on UUID_LEN, such as UUID_LEN * 3. I think the main point is to avoid unbounded scanning on malformed input.
The old code did not have this issue because it only examined as much input as needed based on UUID_LEN. The new fast path starts to use strlen(), so this would be a new risk introduced by the optimization.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-14 23:33 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Chao Li <li.evan.chao@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Masahiko Sawada @ 2026-08-14 23:33 UTC (permalink / raw)
To: Chao Li <li.evan.chao@gmail.com>; +Cc: Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
On Thu, Aug 6, 2026 at 8:07 PM Chao Li <li.evan.chao@gmail.com> wrote:
>
>
>
> > On Aug 7, 2026, at 00:09, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >
> > On Wed, Aug 5, 2026 at 8:40 PM Chao Li <li.evan.chao@gmail.com> wrote:
> >>
> >>
> >>
> >>> On Aug 6, 2026, at 08:19, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>
> >>> On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <tristan.yim@gmail.com> wrote:
> >>>>
> >>>> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>>>
> >>>>> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >>>>>>
> >>>>>> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>>>>>
> >>>>>>> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >>>>>>>>
> >>>>>>>> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>>>>>>>
> >>>>>>>>> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> >>>>>>>>>>
> >>>>>>>>>>
> >>>>>>>>>>
> >>>>>>>>>> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >>>>>>>>>>>
> >>>>>>>>>>> Hi all,
> >>>>>>>>>>>
> >>>>>>>>>>> I'd like to propose the $subject.
> >>>>>>>>>>>
> >>>>>>>>>>> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> >>>>>>>>>>> a run of hex digits is now fast. The attached patch reuses
> >>>>>>>>>>> hex_decode_safe() in the UUID input function to speed up parsing.
> >>>>>>>>>>>
> >>>>>>>>>>> We accept several textual forms of a UUID[1]. The fast path handles
> >>>>>>>>>>> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> >>>>>>>>>>> (where "nx" means n hex digits), and either of those wrapped in
> >>>>>>>>>>> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> >>>>>>>>>>>
> >>>>>>>>>>> I've benchmarked the parse speed using the following query:
> >>>>>>>>>>>
> >>>>>>>>>>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> >>>>>>>>>>> generate_series(1, 1000000);
> >>>>>>>>>>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> >>>>>>>>>>>
> >>>>>>>>>>> I compared the execution time of the second query, which measures
> >>>>>>>>>>> uuid_in() alone, with/without SIMD optimization. Here are results (the
> >>>>>>>>>>> median of 5 runs):
> >>>>>>>>>>>
> >>>>>>>>>>> HEAD: 208.879 ms
> >>>>>>>>>>> Patched: 40.983 ms
> >>>>>>>>>>>
> >>>>>>>>>>> The improvements look promising to me. But in a realistic pipeline the
> >>>>>>>>>>> parse is a small fraction of the work, so end-to-end gains could be
> >>>>>>>>>>> much smaller.
> >>>>>>>>>>>
> >>>>>>>>>>> Feedback is very welcome.
> >>>>>>>>>>>
> >>>>>>>>>> I may be missing something, but I wonder whether the fast path is relying on
> >>>>>>>>>> slightly different input semantics from the existing UUID parser.
> >>>>>>>>>>
> >>>>>>>>>> In particular, hex_decode_safe() is not a strict “32 hex characters only”
> >>>>>>>>>> decoder. It skips whitespace, which is fine for its existing callers, but I
> >>>>>>>>>> don’t think UUID input should treat whitespace inside the UUID body as
> >>>>>>>>>> ignorable.
> >>>>>>>>>
> >>>>>>>>> Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> >>>>>>>>> the following UUID value, which is bad:
> >>>>>>>>>
> >>>>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> >>>>>>>>> uuid
> >>>>>>>>> --------------------------------------
> >>>>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00
> >>>>>>>>> (1 row)
> >>>>>>>>>
> >>>>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast path
> >>>>>>>>>> cannot verify that exactly UUID_LEN bytes were produced.
> >>>>>>>>>
> >>>>>>>>> IIUC hex_decode_safe() does return the output length in bytes. So I
> >>>>>>>>> think we can fallback to the scalar UUID parser if
> >>>>>>>>> esctx.error_occurred is true or if the returned value is not 16.
> >>>>>>>>>
> >>>>>>>>
> >>>>>>>> You’re right, I misread that part. Checking both esctx.error_occurred and
> >>>>>>>> the returned length sounds good to me.
> >>>>>>>>
> >>>>>>>>>>
> >>>>>>>>>> So I think it would be safer either to pre-validate that the 32 source
> >>>>>>>>>> characters are all hex digits before calling hex_decode_safe(), or to use a
> >>>>>>>>>> UUID-specific strict hex decoder for this path. After that, a comment
> >>>>>>>>>> explaining why hex_decode_safe() is safe here would make the invariant much
> >>>>>>>>>> clearer.
> >>>>>>>>>
> >>>>>>>>> IIUC hex_decode_simd_helper() accepts only hex digits so we could
> >>>>>>>>> re-use it for UUID parsing. Let me check if the above idea of using
> >>>>>>>>> the return value works for us first.
> >>>>>>>>>
> >>>>>>>>
> >>>>>>>> That sounds reasonable. My main concern was to keep the fast path’s accepted
> >>>>>>>> input set identical to the scalar UUID parser. Falling back when the decoded
> >>>>>>>> length is not UUID_LEN, together with regression tests for whitespace cases,
> >>>>>>>> should address that.
> >>>>>>>>
> >>>>>>>>>>
> >>>>>>>>>> Could you also add a few regression tests for invalid inputs that contain
> >>>>>>>>>> whitespace inside otherwise fast-path-looking UUID strings? For example:
> >>>>>>>>>>
> >>>>>>>>>> ---------------------------------------------------------------
> >>>>>>>>>>
> >>>>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> >>>>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> >>>>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> >>>>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> >>>>>>>>>> ---------------------------------------------------------------
> >>>>>>>>>>
> >>>>>>>>>> These should continue to be rejected in the same way as the scalar parser.
> >>>>>>>>>> Regards,
> >>>>>>>>>
> >>>>>>>>> Agreed.
> >>>>>>>>>
> >>>>>>>
> >>>>>>> I've attached the updated patch.
> >>>>>>>
> >>>>>>> Regards,
> >>>>>>>
> >>>>>>> --
> >>>>>>> Masahiko Sawada
> >>>>>>> Amazon Web Services: https://aws.amazon.com
> >>>>>>
> >>>>>> I noticed a few typos in the comments:
> >>>>>>
> >>>>>> src/backend/utils/adt/uuid.c
> >>>>>> line 56: “scalar implmentation” -> “scalar implementation”
> >>>>>> line 109: “swalled” -> “swallowed”
> >>>>>> line 110: “kepping” -> “keeping”
> >>>>>> line 118: “grammer” -> “grammar”
> >>>>>> line 119: “whitespaces” -> “whitespace”
> >>>>>>
> >>>>>> Could you fix them ?
> >>>>>
> >>>>> Oops, I fixed them and rechecked other places.
> >>>>>
> >>>>> I've attached the updated patch.
> >>>>>
> >>>>> Regards,
> >>>>>
> >>>>> --
> >>>>> Masahiko Sawada
> >>>>> Amazon Web Services: https://aws.amazon.com
> >>>>
> >>>> The code looks good to me now. I only noticed one small typo in the
> >>>> commit trailer: Reviwed-by should be Reviewed-by.
> >>>>
> >>>> Otherwise, it looks good. Thank you for fixing these issues.
> >>>>
> >>>
> >>> After spending more time on this patch, I find out two things:
> >>>
> >>> 1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
> >>> But including port/simd.h seems wrong as it doesn't use any SIMD
> >>> support functions.
> >>>
> >>> 2. hex_decode_safe() is faster than the current UUID parse
> >>> (isxdigit()+strtoul() approach) even without SIMD. I've created a
> >>> small benchmark test tool (attached as 0002 patch, not intended to be
> >>> pushed into the core), and measures UUID parsing performance of three
> >>> approaches: 'scalar' is the current string_to_uuid() that uses
> >>> isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
> >>> 'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
> >>> UUIDs. Here are results:
> >>>
> >>> =# select path, shape, n_inputs, best_ms::numeric(10,3) from
> >>> uuid_parse_bench(100000, 5);
> >>> path | shape | n_inputs | best_ms
> >>> --------+------------------+----------+---------
> >>> scalar | canonical | 100000 | 22.661
> >>> simd | canonical | 100000 | 1.400
> >>> nosimd | canonical | 100000 | 1.652
> >>> scalar | bare32 | 100000 | 15.932
> >>> simd | bare32 | 100000 | 0.471
> >>> nosimd | bare32 | 100000 | 1.110
> >>> scalar | braced_canonical | 100000 | 17.330
> >>> simd | braced_canonical | 100000 | 1.088
> >>> nosimd | braced_canonical | 100000 | 1.314
> >>> scalar | braced_bare32 | 100000 | 15.942
> >>> simd | braced_bare32 | 100000 | 0.488
> >>> nosimd | braced_bare32 | 100000 | 1.141
> >>> scalar | dashed4 | 100000 | 16.185
> >>> simd | dashed4 | 100000 | 16.493
> >>> nosimd | dashed4 | 100000 | 16.403
> >>> scalar | invalid_hex | 100000 | 0.199
> >>> simd | invalid_hex | 100000 | 1.150
> >>> nosimd | invalid_hex | 100000 | 0.385
> >>> (18 rows)
> >>>
> >>> Each of shape means:
> >>> - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
> >>> - 'bare32': 32 contiguous hex digits
> >>> - 'braced_canonical': {8x-4x-4x-4x-12x}
> >>> - 'braced_bare32': {32 hex digits}
> >>> - 'dashed4': dash after every group of 4
> >>> - 'invalid_hdx': canonical but with a invalid digit
> >>>
> >>> 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
> >>> mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
> >>> and 'nosimd' fall back to the 'scalar' case. According to these
> >>> results, my conclusion is that we can use hex_decode_safe() for
> >>> canonical forms and 32 contiguous hex forms anyway, and let
> >>> hex_decode_safe() choose whether to use SIMD. We would win in either
> >>> case. We still use the current scalar approach for uncommon UUID forms
> >>> and error reporting purposes.
> >>>
> >>> Regards,
> >>>
> >>> --
> >>> Masahiko Sawada
> >>> Amazon Web Services: https://aws.amazon.com
> >>> <v4-0001-Optimize-UUID-parse-using-SIMD.patch><v4-0002-uuid_parse_bench-module.patch>
> >>
> >> A few comments on v4.
> >>
> >> 1 - 0001
> >> ```
> >> +static void
> >> +string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
> >> +{
> >> + const char *body = source;
> >> + size_t len = strlen(source);
> >> ```
> >>
> >> I think it would be better to avoid strlen(). The old code processes at most UUID_LEN (16) byte pairs, so it does not need to scan arbitrarily far on malformed input. So, maybe we could use something like strnlen(source, 39) instead.
> >
> > While strnlen(source, 39) works there, 39 is a magic number and it's
> > tied to the current format check logic. What is the benefit of using
> > strnlen(source, 39) instead? I'm not sure it warrants having the magic
> > number.
>
> It doesn't have to be exactly 39; 1024 (long enough) would also work, or perhaps something based on UUID_LEN, such as UUID_LEN * 3. I think the main point is to avoid unbounded scanning on malformed input.
>
> The old code did not have this issue because it only examined as much input as needed based on UUID_LEN. The new fast path starts to use strlen(), so this would be a new risk introduced by the optimization.
I don't think the scan can be really unbounded. string_to_uuid()
receives a cstring, so by the time it is called the caller has already
walked or copied the whole string to produce it. So unless the
unbounded scan can be reached in some path I have overlooked, I'd
prefer to keep strlen() here. Happy to change it if you still think it
is worth it.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-15 01:08 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
0 siblings, 2 replies; 27+ messages in thread
From: Masahiko Sawada @ 2026-08-15 01:08 UTC (permalink / raw)
To: Chao Li <li.evan.chao@gmail.com>; +Cc: Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
On Fri, Aug 14, 2026 at 4:33 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> On Thu, Aug 6, 2026 at 8:07 PM Chao Li <li.evan.chao@gmail.com> wrote:
> >
> >
> >
> > > On Aug 7, 2026, at 00:09, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >
> > > On Wed, Aug 5, 2026 at 8:40 PM Chao Li <li.evan.chao@gmail.com> wrote:
> > >>
> > >>
> > >>
> > >>> On Aug 6, 2026, at 08:19, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >>>
> > >>> On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <tristan.yim@gmail.com> wrote:
> > >>>>
> > >>>> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >>>>>
> > >>>>> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > >>>>>>
> > >>>>>> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >>>>>>>
> > >>>>>>> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > >>>>>>>>
> > >>>>>>>> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >>>>>>>>>
> > >>>>>>>>> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > >>>>>>>>>>
> > >>>>>>>>>>
> > >>>>>>>>>>
> > >>>>>>>>>> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >>>>>>>>>>>
> > >>>>>>>>>>> Hi all,
> > >>>>>>>>>>>
> > >>>>>>>>>>> I'd like to propose the $subject.
> > >>>>>>>>>>>
> > >>>>>>>>>>> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> > >>>>>>>>>>> a run of hex digits is now fast. The attached patch reuses
> > >>>>>>>>>>> hex_decode_safe() in the UUID input function to speed up parsing.
> > >>>>>>>>>>>
> > >>>>>>>>>>> We accept several textual forms of a UUID[1]. The fast path handles
> > >>>>>>>>>>> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> > >>>>>>>>>>> (where "nx" means n hex digits), and either of those wrapped in
> > >>>>>>>>>>> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> > >>>>>>>>>>>
> > >>>>>>>>>>> I've benchmarked the parse speed using the following query:
> > >>>>>>>>>>>
> > >>>>>>>>>>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> > >>>>>>>>>>> generate_series(1, 1000000);
> > >>>>>>>>>>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> > >>>>>>>>>>>
> > >>>>>>>>>>> I compared the execution time of the second query, which measures
> > >>>>>>>>>>> uuid_in() alone, with/without SIMD optimization. Here are results (the
> > >>>>>>>>>>> median of 5 runs):
> > >>>>>>>>>>>
> > >>>>>>>>>>> HEAD: 208.879 ms
> > >>>>>>>>>>> Patched: 40.983 ms
> > >>>>>>>>>>>
> > >>>>>>>>>>> The improvements look promising to me. But in a realistic pipeline the
> > >>>>>>>>>>> parse is a small fraction of the work, so end-to-end gains could be
> > >>>>>>>>>>> much smaller.
> > >>>>>>>>>>>
> > >>>>>>>>>>> Feedback is very welcome.
> > >>>>>>>>>>>
> > >>>>>>>>>> I may be missing something, but I wonder whether the fast path is relying on
> > >>>>>>>>>> slightly different input semantics from the existing UUID parser.
> > >>>>>>>>>>
> > >>>>>>>>>> In particular, hex_decode_safe() is not a strict “32 hex characters only”
> > >>>>>>>>>> decoder. It skips whitespace, which is fine for its existing callers, but I
> > >>>>>>>>>> don’t think UUID input should treat whitespace inside the UUID body as
> > >>>>>>>>>> ignorable.
> > >>>>>>>>>
> > >>>>>>>>> Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> > >>>>>>>>> the following UUID value, which is bad:
> > >>>>>>>>>
> > >>>>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> > >>>>>>>>> uuid
> > >>>>>>>>> --------------------------------------
> > >>>>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00
> > >>>>>>>>> (1 row)
> > >>>>>>>>>
> > >>>>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast path
> > >>>>>>>>>> cannot verify that exactly UUID_LEN bytes were produced.
> > >>>>>>>>>
> > >>>>>>>>> IIUC hex_decode_safe() does return the output length in bytes. So I
> > >>>>>>>>> think we can fallback to the scalar UUID parser if
> > >>>>>>>>> esctx.error_occurred is true or if the returned value is not 16.
> > >>>>>>>>>
> > >>>>>>>>
> > >>>>>>>> You’re right, I misread that part. Checking both esctx.error_occurred and
> > >>>>>>>> the returned length sounds good to me.
> > >>>>>>>>
> > >>>>>>>>>>
> > >>>>>>>>>> So I think it would be safer either to pre-validate that the 32 source
> > >>>>>>>>>> characters are all hex digits before calling hex_decode_safe(), or to use a
> > >>>>>>>>>> UUID-specific strict hex decoder for this path. After that, a comment
> > >>>>>>>>>> explaining why hex_decode_safe() is safe here would make the invariant much
> > >>>>>>>>>> clearer.
> > >>>>>>>>>
> > >>>>>>>>> IIUC hex_decode_simd_helper() accepts only hex digits so we could
> > >>>>>>>>> re-use it for UUID parsing. Let me check if the above idea of using
> > >>>>>>>>> the return value works for us first.
> > >>>>>>>>>
> > >>>>>>>>
> > >>>>>>>> That sounds reasonable. My main concern was to keep the fast path’s accepted
> > >>>>>>>> input set identical to the scalar UUID parser. Falling back when the decoded
> > >>>>>>>> length is not UUID_LEN, together with regression tests for whitespace cases,
> > >>>>>>>> should address that.
> > >>>>>>>>
> > >>>>>>>>>>
> > >>>>>>>>>> Could you also add a few regression tests for invalid inputs that contain
> > >>>>>>>>>> whitespace inside otherwise fast-path-looking UUID strings? For example:
> > >>>>>>>>>>
> > >>>>>>>>>> ---------------------------------------------------------------
> > >>>>>>>>>>
> > >>>>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > >>>>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > >>>>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > >>>>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > >>>>>>>>>> ---------------------------------------------------------------
> > >>>>>>>>>>
> > >>>>>>>>>> These should continue to be rejected in the same way as the scalar parser.
> > >>>>>>>>>> Regards,
> > >>>>>>>>>
> > >>>>>>>>> Agreed.
> > >>>>>>>>>
> > >>>>>>>
> > >>>>>>> I've attached the updated patch.
> > >>>>>>>
> > >>>>>>> Regards,
> > >>>>>>>
> > >>>>>>> --
> > >>>>>>> Masahiko Sawada
> > >>>>>>> Amazon Web Services: https://aws.amazon.com
> > >>>>>>
> > >>>>>> I noticed a few typos in the comments:
> > >>>>>>
> > >>>>>> src/backend/utils/adt/uuid.c
> > >>>>>> line 56: “scalar implmentation” -> “scalar implementation”
> > >>>>>> line 109: “swalled” -> “swallowed”
> > >>>>>> line 110: “kepping” -> “keeping”
> > >>>>>> line 118: “grammer” -> “grammar”
> > >>>>>> line 119: “whitespaces” -> “whitespace”
> > >>>>>>
> > >>>>>> Could you fix them ?
> > >>>>>
> > >>>>> Oops, I fixed them and rechecked other places.
> > >>>>>
> > >>>>> I've attached the updated patch.
> > >>>>>
> > >>>>> Regards,
> > >>>>>
> > >>>>> --
> > >>>>> Masahiko Sawada
> > >>>>> Amazon Web Services: https://aws.amazon.com
> > >>>>
> > >>>> The code looks good to me now. I only noticed one small typo in the
> > >>>> commit trailer: Reviwed-by should be Reviewed-by.
> > >>>>
> > >>>> Otherwise, it looks good. Thank you for fixing these issues.
> > >>>>
> > >>>
> > >>> After spending more time on this patch, I find out two things:
> > >>>
> > >>> 1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
> > >>> But including port/simd.h seems wrong as it doesn't use any SIMD
> > >>> support functions.
> > >>>
> > >>> 2. hex_decode_safe() is faster than the current UUID parse
> > >>> (isxdigit()+strtoul() approach) even without SIMD. I've created a
> > >>> small benchmark test tool (attached as 0002 patch, not intended to be
> > >>> pushed into the core), and measures UUID parsing performance of three
> > >>> approaches: 'scalar' is the current string_to_uuid() that uses
> > >>> isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
> > >>> 'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
> > >>> UUIDs. Here are results:
> > >>>
> > >>> =# select path, shape, n_inputs, best_ms::numeric(10,3) from
> > >>> uuid_parse_bench(100000, 5);
> > >>> path | shape | n_inputs | best_ms
> > >>> --------+------------------+----------+---------
> > >>> scalar | canonical | 100000 | 22.661
> > >>> simd | canonical | 100000 | 1.400
> > >>> nosimd | canonical | 100000 | 1.652
> > >>> scalar | bare32 | 100000 | 15.932
> > >>> simd | bare32 | 100000 | 0.471
> > >>> nosimd | bare32 | 100000 | 1.110
> > >>> scalar | braced_canonical | 100000 | 17.330
> > >>> simd | braced_canonical | 100000 | 1.088
> > >>> nosimd | braced_canonical | 100000 | 1.314
> > >>> scalar | braced_bare32 | 100000 | 15.942
> > >>> simd | braced_bare32 | 100000 | 0.488
> > >>> nosimd | braced_bare32 | 100000 | 1.141
> > >>> scalar | dashed4 | 100000 | 16.185
> > >>> simd | dashed4 | 100000 | 16.493
> > >>> nosimd | dashed4 | 100000 | 16.403
> > >>> scalar | invalid_hex | 100000 | 0.199
> > >>> simd | invalid_hex | 100000 | 1.150
> > >>> nosimd | invalid_hex | 100000 | 0.385
> > >>> (18 rows)
> > >>>
> > >>> Each of shape means:
> > >>> - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
> > >>> - 'bare32': 32 contiguous hex digits
> > >>> - 'braced_canonical': {8x-4x-4x-4x-12x}
> > >>> - 'braced_bare32': {32 hex digits}
> > >>> - 'dashed4': dash after every group of 4
> > >>> - 'invalid_hdx': canonical but with a invalid digit
> > >>>
> > >>> 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
> > >>> mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
> > >>> and 'nosimd' fall back to the 'scalar' case. According to these
> > >>> results, my conclusion is that we can use hex_decode_safe() for
> > >>> canonical forms and 32 contiguous hex forms anyway, and let
> > >>> hex_decode_safe() choose whether to use SIMD. We would win in either
> > >>> case. We still use the current scalar approach for uncommon UUID forms
> > >>> and error reporting purposes.
> > >>>
> > >>> Regards,
> > >>>
> > >>> --
> > >>> Masahiko Sawada
> > >>> Amazon Web Services: https://aws.amazon.com
> > >>> <v4-0001-Optimize-UUID-parse-using-SIMD.patch><v4-0002-uuid_parse_bench-module.patch>
> > >>
> > >> A few comments on v4.
> > >>
> > >> 1 - 0001
> > >> ```
> > >> +static void
> > >> +string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
> > >> +{
> > >> + const char *body = source;
> > >> + size_t len = strlen(source);
> > >> ```
> > >>
> > >> I think it would be better to avoid strlen(). The old code processes at most UUID_LEN (16) byte pairs, so it does not need to scan arbitrarily far on malformed input. So, maybe we could use something like strnlen(source, 39) instead.
> > >
> > > While strnlen(source, 39) works there, 39 is a magic number and it's
> > > tied to the current format check logic. What is the benefit of using
> > > strnlen(source, 39) instead? I'm not sure it warrants having the magic
> > > number.
> >
> > It doesn't have to be exactly 39; 1024 (long enough) would also work, or perhaps something based on UUID_LEN, such as UUID_LEN * 3. I think the main point is to avoid unbounded scanning on malformed input.
> >
> > The old code did not have this issue because it only examined as much input as needed based on UUID_LEN. The new fast path starts to use strlen(), so this would be a new risk introduced by the optimization.
>
> I don't think the scan can be really unbounded. string_to_uuid()
> receives a cstring, so by the time it is called the caller has already
> walked or copied the whole string to produce it. So unless the
> unbounded scan can be reached in some path I have overlooked, I'd
> prefer to keep strlen() here. Happy to change it if you still think it
> is worth it.
After more thoughts, while I still don't think the scan can be
unbounded, using strlen() would add an extra scan just to determine we
use hex_decode_safe(). I'll change it to use strnlen() instead.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-16 00:59 Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
1 sibling, 0 replies; 27+ messages in thread
From: Bharath Rupireddy @ 2026-08-16 00:59 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: Chao Li <li.evan.chao@gmail.com>; Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
Hi,
On Fri, Aug 14, 2026 at 6:08 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> > > >> +static void
> > > >> +string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
> > > >> +{
> > > >> + const char *body = source;
> > > >> + size_t len = strlen(source);
> > > >> ```
> > > >>
> > > >> I think it would be better to avoid strlen(). The old code processes at most UUID_LEN (16) byte pairs, so it does not need to scan arbitrarily far on malformed input. So, maybe we could use something like strnlen(source, 39) instead.
> > > >
> > > > While strnlen(source, 39) works there, 39 is a magic number and it's
> > > > tied to the current format check logic. What is the benefit of using
> > > > strnlen(source, 39) instead? I'm not sure it warrants having the magic
> > > > number.
> > >
> > > It doesn't have to be exactly 39; 1024 (long enough) would also work, or perhaps something based on UUID_LEN, such as UUID_LEN * 3. I think the main point is to avoid unbounded scanning on malformed input.
> > >
> > > The old code did not have this issue because it only examined as much input as needed based on UUID_LEN. The new fast path starts to use strlen(), so this would be a new risk introduced by the optimization.
> >
> > I don't think the scan can be really unbounded. string_to_uuid()
> > receives a cstring, so by the time it is called the caller has already
> > walked or copied the whole string to produce it. So unless the
> > unbounded scan can be reached in some path I have overlooked, I'd
> > prefer to keep strlen() here. Happy to change it if you still think it
> > is worth it.
>
> After more thoughts, while I still don't think the scan can be
> unbounded, using strlen() would add an extra scan just to determine we
> use hex_decode_safe(). I'll change it to use strnlen() instead.
+1 to use strnlen() instead of strlen() just for the simple reason
that it's better not to scan malformed-yet-null-terminated input
strings (say a null-terminated input of 1GB).
DO $$ DECLARE s text := repeat('a', 1000000000); r uuid;
BEGIN r := s::uuid; EXCEPTION WHEN others THEN NULL; END $$;
(gdb) p strlen(uuid_str)
$2 = 1000000000
Instead of N * UUID_LEN in strnlen, NAMEDATALEN also works unless I'm
missing anything.
--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-17 22:20 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
1 sibling, 4 replies; 27+ messages in thread
From: Masahiko Sawada @ 2026-08-17 22:20 UTC (permalink / raw)
To: Chao Li <li.evan.chao@gmail.com>; +Cc: Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
On Fri, Aug 14, 2026 at 6:08 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> On Fri, Aug 14, 2026 at 4:33 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >
> > On Thu, Aug 6, 2026 at 8:07 PM Chao Li <li.evan.chao@gmail.com> wrote:
> > >
> > >
> > >
> > > > On Aug 7, 2026, at 00:09, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >
> > > > On Wed, Aug 5, 2026 at 8:40 PM Chao Li <li.evan.chao@gmail.com> wrote:
> > > >>
> > > >>
> > > >>
> > > >>> On Aug 6, 2026, at 08:19, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >>>
> > > >>> On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > >>>>
> > > >>>> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >>>>>
> > > >>>>> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > >>>>>>
> > > >>>>>> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >>>>>>>
> > > >>>>>>> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > >>>>>>>>
> > > >>>>>>>> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >>>>>>>>>
> > > >>>>>>>>> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan.yim@gmail.com> wrote:
> > > >>>>>>>>>>
> > > >>>>>>>>>>
> > > >>>>>>>>>>
> > > >>>>>>>>>> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> Hi all,
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> I'd like to propose the $subject.
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
> > > >>>>>>>>>>> a run of hex digits is now fast. The attached patch reuses
> > > >>>>>>>>>>> hex_decode_safe() in the UUID input function to speed up parsing.
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> We accept several textual forms of a UUID[1]. The fast path handles
> > > >>>>>>>>>>> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
> > > >>>>>>>>>>> (where "nx" means n hex digits), and either of those wrapped in
> > > >>>>>>>>>>> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> I've benchmarked the parse speed using the following query:
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
> > > >>>>>>>>>>> generate_series(1, 1000000);
> > > >>>>>>>>>>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> I compared the execution time of the second query, which measures
> > > >>>>>>>>>>> uuid_in() alone, with/without SIMD optimization. Here are results (the
> > > >>>>>>>>>>> median of 5 runs):
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> HEAD: 208.879 ms
> > > >>>>>>>>>>> Patched: 40.983 ms
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> The improvements look promising to me. But in a realistic pipeline the
> > > >>>>>>>>>>> parse is a small fraction of the work, so end-to-end gains could be
> > > >>>>>>>>>>> much smaller.
> > > >>>>>>>>>>>
> > > >>>>>>>>>>> Feedback is very welcome.
> > > >>>>>>>>>>>
> > > >>>>>>>>>> I may be missing something, but I wonder whether the fast path is relying on
> > > >>>>>>>>>> slightly different input semantics from the existing UUID parser.
> > > >>>>>>>>>>
> > > >>>>>>>>>> In particular, hex_decode_safe() is not a strict “32 hex characters only”
> > > >>>>>>>>>> decoder. It skips whitespace, which is fine for its existing callers, but I
> > > >>>>>>>>>> don’t think UUID input should treat whitespace inside the UUID body as
> > > >>>>>>>>>> ignorable.
> > > >>>>>>>>>
> > > >>>>>>>>> Good catch! hex_decode_safe() skips whitespaces so the patch accepts
> > > >>>>>>>>> the following UUID value, which is bad:
> > > >>>>>>>>>
> > > >>>>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> > > >>>>>>>>> uuid
> > > >>>>>>>>> --------------------------------------
> > > >>>>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00
> > > >>>>>>>>> (1 row)
> > > >>>>>>>>>
> > > >>>>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast path
> > > >>>>>>>>>> cannot verify that exactly UUID_LEN bytes were produced.
> > > >>>>>>>>>
> > > >>>>>>>>> IIUC hex_decode_safe() does return the output length in bytes. So I
> > > >>>>>>>>> think we can fallback to the scalar UUID parser if
> > > >>>>>>>>> esctx.error_occurred is true or if the returned value is not 16.
> > > >>>>>>>>>
> > > >>>>>>>>
> > > >>>>>>>> You’re right, I misread that part. Checking both esctx.error_occurred and
> > > >>>>>>>> the returned length sounds good to me.
> > > >>>>>>>>
> > > >>>>>>>>>>
> > > >>>>>>>>>> So I think it would be safer either to pre-validate that the 32 source
> > > >>>>>>>>>> characters are all hex digits before calling hex_decode_safe(), or to use a
> > > >>>>>>>>>> UUID-specific strict hex decoder for this path. After that, a comment
> > > >>>>>>>>>> explaining why hex_decode_safe() is safe here would make the invariant much
> > > >>>>>>>>>> clearer.
> > > >>>>>>>>>
> > > >>>>>>>>> IIUC hex_decode_simd_helper() accepts only hex digits so we could
> > > >>>>>>>>> re-use it for UUID parsing. Let me check if the above idea of using
> > > >>>>>>>>> the return value works for us first.
> > > >>>>>>>>>
> > > >>>>>>>>
> > > >>>>>>>> That sounds reasonable. My main concern was to keep the fast path’s accepted
> > > >>>>>>>> input set identical to the scalar UUID parser. Falling back when the decoded
> > > >>>>>>>> length is not UUID_LEN, together with regression tests for whitespace cases,
> > > >>>>>>>> should address that.
> > > >>>>>>>>
> > > >>>>>>>>>>
> > > >>>>>>>>>> Could you also add a few regression tests for invalid inputs that contain
> > > >>>>>>>>>> whitespace inside otherwise fast-path-looking UUID strings? For example:
> > > >>>>>>>>>>
> > > >>>>>>>>>> ---------------------------------------------------------------
> > > >>>>>>>>>>
> > > >>>>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > > >>>>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > > >>>>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > > >>>>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > > >>>>>>>>>> ---------------------------------------------------------------
> > > >>>>>>>>>>
> > > >>>>>>>>>> These should continue to be rejected in the same way as the scalar parser.
> > > >>>>>>>>>> Regards,
> > > >>>>>>>>>
> > > >>>>>>>>> Agreed.
> > > >>>>>>>>>
> > > >>>>>>>
> > > >>>>>>> I've attached the updated patch.
> > > >>>>>>>
> > > >>>>>>> Regards,
> > > >>>>>>>
> > > >>>>>>> --
> > > >>>>>>> Masahiko Sawada
> > > >>>>>>> Amazon Web Services: https://aws.amazon.com
> > > >>>>>>
> > > >>>>>> I noticed a few typos in the comments:
> > > >>>>>>
> > > >>>>>> src/backend/utils/adt/uuid.c
> > > >>>>>> line 56: “scalar implmentation” -> “scalar implementation”
> > > >>>>>> line 109: “swalled” -> “swallowed”
> > > >>>>>> line 110: “kepping” -> “keeping”
> > > >>>>>> line 118: “grammer” -> “grammar”
> > > >>>>>> line 119: “whitespaces” -> “whitespace”
> > > >>>>>>
> > > >>>>>> Could you fix them ?
> > > >>>>>
> > > >>>>> Oops, I fixed them and rechecked other places.
> > > >>>>>
> > > >>>>> I've attached the updated patch.
> > > >>>>>
> > > >>>>> Regards,
> > > >>>>>
> > > >>>>> --
> > > >>>>> Masahiko Sawada
> > > >>>>> Amazon Web Services: https://aws.amazon.com
> > > >>>>
> > > >>>> The code looks good to me now. I only noticed one small typo in the
> > > >>>> commit trailer: Reviwed-by should be Reviewed-by.
> > > >>>>
> > > >>>> Otherwise, it looks good. Thank you for fixing these issues.
> > > >>>>
> > > >>>
> > > >>> After spending more time on this patch, I find out two things:
> > > >>>
> > > >>> 1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
> > > >>> But including port/simd.h seems wrong as it doesn't use any SIMD
> > > >>> support functions.
> > > >>>
> > > >>> 2. hex_decode_safe() is faster than the current UUID parse
> > > >>> (isxdigit()+strtoul() approach) even without SIMD. I've created a
> > > >>> small benchmark test tool (attached as 0002 patch, not intended to be
> > > >>> pushed into the core), and measures UUID parsing performance of three
> > > >>> approaches: 'scalar' is the current string_to_uuid() that uses
> > > >>> isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
> > > >>> 'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
> > > >>> UUIDs. Here are results:
> > > >>>
> > > >>> =# select path, shape, n_inputs, best_ms::numeric(10,3) from
> > > >>> uuid_parse_bench(100000, 5);
> > > >>> path | shape | n_inputs | best_ms
> > > >>> --------+------------------+----------+---------
> > > >>> scalar | canonical | 100000 | 22.661
> > > >>> simd | canonical | 100000 | 1.400
> > > >>> nosimd | canonical | 100000 | 1.652
> > > >>> scalar | bare32 | 100000 | 15.932
> > > >>> simd | bare32 | 100000 | 0.471
> > > >>> nosimd | bare32 | 100000 | 1.110
> > > >>> scalar | braced_canonical | 100000 | 17.330
> > > >>> simd | braced_canonical | 100000 | 1.088
> > > >>> nosimd | braced_canonical | 100000 | 1.314
> > > >>> scalar | braced_bare32 | 100000 | 15.942
> > > >>> simd | braced_bare32 | 100000 | 0.488
> > > >>> nosimd | braced_bare32 | 100000 | 1.141
> > > >>> scalar | dashed4 | 100000 | 16.185
> > > >>> simd | dashed4 | 100000 | 16.493
> > > >>> nosimd | dashed4 | 100000 | 16.403
> > > >>> scalar | invalid_hex | 100000 | 0.199
> > > >>> simd | invalid_hex | 100000 | 1.150
> > > >>> nosimd | invalid_hex | 100000 | 0.385
> > > >>> (18 rows)
> > > >>>
> > > >>> Each of shape means:
> > > >>> - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
> > > >>> - 'bare32': 32 contiguous hex digits
> > > >>> - 'braced_canonical': {8x-4x-4x-4x-12x}
> > > >>> - 'braced_bare32': {32 hex digits}
> > > >>> - 'dashed4': dash after every group of 4
> > > >>> - 'invalid_hdx': canonical but with a invalid digit
> > > >>>
> > > >>> 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
> > > >>> mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
> > > >>> and 'nosimd' fall back to the 'scalar' case. According to these
> > > >>> results, my conclusion is that we can use hex_decode_safe() for
> > > >>> canonical forms and 32 contiguous hex forms anyway, and let
> > > >>> hex_decode_safe() choose whether to use SIMD. We would win in either
> > > >>> case. We still use the current scalar approach for uncommon UUID forms
> > > >>> and error reporting purposes.
> > > >>>
> > > >>> Regards,
> > > >>>
> > > >>> --
> > > >>> Masahiko Sawada
> > > >>> Amazon Web Services: https://aws.amazon.com
> > > >>> <v4-0001-Optimize-UUID-parse-using-SIMD.patch><v4-0002-uuid_parse_bench-module.patch>
> > > >>
> > > >> A few comments on v4.
> > > >>
> > > >> 1 - 0001
> > > >> ```
> > > >> +static void
> > > >> +string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
> > > >> +{
> > > >> + const char *body = source;
> > > >> + size_t len = strlen(source);
> > > >> ```
> > > >>
> > > >> I think it would be better to avoid strlen(). The old code processes at most UUID_LEN (16) byte pairs, so it does not need to scan arbitrarily far on malformed input. So, maybe we could use something like strnlen(source, 39) instead.
> > > >
> > > > While strnlen(source, 39) works there, 39 is a magic number and it's
> > > > tied to the current format check logic. What is the benefit of using
> > > > strnlen(source, 39) instead? I'm not sure it warrants having the magic
> > > > number.
> > >
> > > It doesn't have to be exactly 39; 1024 (long enough) would also work, or perhaps something based on UUID_LEN, such as UUID_LEN * 3. I think the main point is to avoid unbounded scanning on malformed input.
> > >
> > > The old code did not have this issue because it only examined as much input as needed based on UUID_LEN. The new fast path starts to use strlen(), so this would be a new risk introduced by the optimization.
> >
> > I don't think the scan can be really unbounded. string_to_uuid()
> > receives a cstring, so by the time it is called the caller has already
> > walked or copied the whole string to produce it. So unless the
> > unbounded scan can be reached in some path I have overlooked, I'd
> > prefer to keep strlen() here. Happy to change it if you still think it
> > is worth it.
>
> After more thoughts, while I still don't think the scan can be
> unbounded, using strlen() would add an extra scan just to determine we
> use hex_decode_safe(). I'll change it to use strnlen() instead.
I've updated the patch accordingly. Please review it.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-patch] v5-0001-Optimize-UUID-parse-using-SIMD.patch (12.1K, ../../CAD21AoDpMR7=emNWSepAg6A1q-45S-epduDkEyR4Tt4TYgjkMQ@mail.gmail.com/2-v5-0001-Optimize-UUID-parse-using-SIMD.patch)
download | inline diff:
From 52e9030780fadcaf905062ceefecb4f87737ab9d Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 25 Jun 2026 10:03:44 -0700
Subject: [PATCH v5 1/2] Optimize UUID parse using SIMD.
Previously, string_to_uuid() decoded one byte at a time, calling
isxdigit() twice and strtoul() once for every pair of hexadecimal
digits. That loop dominated the cost of uuid_in().
This commit adds a fast path for the two common shapes: a bare string
of 32 hexadecimal digits, and the canonical 8x-4x-4x-4x-12x form
(where "nx" means n hexadecimal digits), each optionally wrapped in
braces. Both are compacted into 32 contiguous hexadecimal digits and
decoded with hex_decode_safe(). Any other shape, or any decoding
error, is handed off to the original scalar parser, now
string_to_uuid_scalar(), so the accepted grammar and the error
messages are unchanged.
hex_decode_safe() silently skips whitespace while the UUID grammar
does not, so a decode can succeed and still write fewer than UUID_LEN
bytes. The fast path therefore treats a short result as a failure,
just like an error, and lets the scalar parser reject the input and
report the syntax error.
The fast path is deliberately not conditional on SIMD support.
hex_decode_safe() selects a vectorized or scalar implementation
itself, and even its scalar implementation is an order of magnitude
faster than decoding a byte at a time, so gating this on USE_NO_SIMD
would only penalize platforms that have neither SSE2 nor NEON.
Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Discussion: https://postgr.es/m/CAD21AoCqeR4UQU77Q_yOMNNzJ7AVeiO5QZT+4HnzPm4Wm-e02Q@mail.gmail.com
---
src/backend/utils/adt/uuid.c | 103 +++++++++++++++++++++++++++--
src/test/regress/expected/uuid.out | 75 +++++++++++++++++++++
src/test/regress/sql/uuid.sql | 25 +++++++
3 files changed, 198 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 28e18940a9d..9a3b6715828 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -19,7 +19,9 @@
#include "common/hashfn.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
#include "port/pg_bswap.h"
+#include "utils/builtins.h"
#include "utils/fmgrprotos.h"
#include "utils/guc.h"
#include "utils/skipsupport.h"
@@ -139,13 +141,13 @@ uuid_out(PG_FUNCTION_ARGS)
}
/*
- * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
- * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
- * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
- * digits, is the only one used for output.)
+ * Reference implementation of the UUID grammar, parsing one character at a
+ * time. string_to_uuid() recognizes the common shapes more cheaply and
+ * defers to this function for everything else, so this is also the only
+ * place that reports a syntax error.
*/
static void
-string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+string_to_uuid_scalar(const char *source, pg_uuid_t *uuid, Node *escontext)
{
const char *src = source;
bool braces = false;
@@ -194,6 +196,97 @@ syntax_error:
"uuid", source)));
}
+/*
+ * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
+ * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
+ * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
+ * digits, is the only one used for output.)
+ *
+ * The two common shapes -- a bare string of 32 hexadecimal digits and the
+ * canonical form, each optionally wrapped in braces -- are compacted into 32
+ * contiguous hex digits and decoded with hex_decode_safe(), which is much
+ * faster than the character-at-a-time loop. Any other shape, or any decoding
+ * error, is handed off to string_to_uuid_scalar() so that the accepted
+ * grammar and the error messages are unchanged.
+ *
+ * Note that this fast path is not conditional on SIMD support:
+ * hex_decode_safe() picks a vectorized or scalar implementation itself, and
+ * even its scalar implementation is far faster than string_to_uuid_scalar().
+ */
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+ const char *body = source;
+ const char *hexsrc = NULL;
+ char hexbuf[32];
+ uint64 written;
+ size_t len;
+ ErrorSaveContext esctx = {T_ErrorSaveContext};
+
+ /*
+ * Measure the input only far enough to classify its shape. The bound
+ * must exceed the longest shape handled here, the braced canonical form
+ * at 38 characters: strnlen() returns the bound for anything at least
+ * that long, so stopping at an accepted length would accept a longer
+ * string that merely starts with a valid UUID.
+ */
+ len = strnlen(source, 64);
+
+ /* Strip one optional surrounding brace pair */
+ if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
+ {
+ body = source + 1;
+ len -= 2;
+ }
+
+ if (len == 32)
+ {
+ /*
+ * Body is already 32 contiguous hex digits -- decode straight from
+ * the input. hex_decode_safe() reads exactly body[0..31], so it never
+ * touches the trailing NULL or '}'.
+ */
+ hexsrc = body;
+ }
+ else if (len == 36 && body[8] == '-' && body[13] == '-' &&
+ body[18] == '-' && body[23] == '-')
+ {
+ /*
+ * Canonical 8x-4x-4x-4x-12x form; compact them into hexbuf with
+ * fixed-offset copies, dropping the dashes.
+ */
+ memcpy(&hexbuf[0], &body[0], 8);
+ memcpy(&hexbuf[8], &body[9], 4);
+ memcpy(&hexbuf[12], &body[14], 4);
+ memcpy(&hexbuf[16], &body[19], 4);
+ memcpy(&hexbuf[20], &body[24], 12);
+ hexsrc = hexbuf;
+ }
+
+ if (hexsrc == NULL)
+ {
+ /* Uncommon shape; let the general parse handle it */
+ string_to_uuid_scalar(source, uuid, escontext);
+ return;
+ }
+
+ /*
+ * Decode the UUID hex data using our hex decoder that is SIMD-aware. We
+ * give it a private error context so that a decode failure is swallowed
+ * here and reported by the scalar path instead, keeping the error message
+ * identical.
+ */
+ written = hex_decode_safe(hexsrc, 32, (char *) uuid->data, (Node *) &esctx);
+
+ /*
+ * Fall back to the scalar path on any error. We must also reject a short
+ * result: hex_decode_safe() skips whitespace, so it can succeed yet write
+ * fewer than UUID_LEN bytes, whereas the UUID grammar forbids whitespace.
+ */
+ if (esctx.error_occurred || written != UUID_LEN)
+ string_to_uuid_scalar(source, uuid, escontext);
+}
+
Datum
uuid_recv(PG_FUNCTION_ARGS)
{
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index d542eb14b26..6247bae8574 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -375,5 +375,80 @@ SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
t
(1 row)
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f32 99c6770"
+LINE 1: SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ ^
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c67 "
+LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ ^
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: " 35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "AZ35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a-7143-4912-9b55-f322699c6770}"
+LINE 1: SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c6770}"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c67 }"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ ^
+-- The parser only measures the input far enough to classify its shape. If it
+-- stopped measuring at one of the accepted lengths, a longer string that
+-- merely starts with a valid UUID would look like that UUID and be accepted
+-- with the rest silently ignored, so check that trailing data is rejected.
+SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f322699c6770TRAILING"
+LINE 1: SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
+ ^
+SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
+ERROR: invalid input syntax for type uuid: "{5b35380a714349129b55f322699c6770}TRAILING"
+LINE 1: SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
+ ^
+SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c6770TRAILING"
+LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
+ ^
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uuid;
+ERROR: invalid input syntax for type uuid: "{5b35380a-7143-4912-9b55-f322699c6770}TRAILING"
+LINE 1: SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uui...
+ ^
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
index 54f0d8f8255..d13e8c21921 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -178,5 +178,30 @@ SELECT '\x019a2f859ced7225b99d9c55044a2563'::bytea::uuid;
SELECT '\x1234567890abcdef'::bytea::uuid; -- error
SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+
+-- The parser only measures the input far enough to classify its shape. If it
+-- stopped measuring at one of the accepted lengths, a longer string that
+-- merely starts with a valid UUID would look like that UUID and be accepted
+-- with the rest silently ignored, so check that trailing data is rejected.
+SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
+SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
+SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uuid;
+
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
--
2.55.0
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-17 23:45 Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
3 siblings, 0 replies; 27+ messages in thread
From: Bharath Rupireddy @ 2026-08-17 23:45 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: Chao Li <li.evan.chao@gmail.com>; Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
Hi,
On Mon, Aug 17, 2026 at 3:21 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> > > > It doesn't have to be exactly 39; 1024 (long enough) would also work, or perhaps something based on UUID_LEN, such as UUID_LEN * 3. I think the main point is to avoid unbounded scanning on malformed input.
> > > >
> > > > The old code did not have this issue because it only examined as much input as needed based on UUID_LEN. The new fast path starts to use strlen(), so this would be a new risk introduced by the optimization.
> > >
> > > I don't think the scan can be really unbounded. string_to_uuid()
> > > receives a cstring, so by the time it is called the caller has already
> > > walked or copied the whole string to produce it. So unless the
> > > unbounded scan can be reached in some path I have overlooked, I'd
> > > prefer to keep strlen() here. Happy to change it if you still think it
> > > is worth it.
> >
> > After more thoughts, while I still don't think the scan can be
> > unbounded, using strlen() would add an extra scan just to determine we
> > use hex_decode_safe(). I'll change it to use strnlen() instead.
>
> I've updated the patch accordingly. Please review it.
I reviewed the diff and the v5 patch looks good. Bounding the strlen
to 64 bytes seems fine. pgindent, make check, and make check-world are
all clean.
--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-18 00:17 Chao Li <li.evan.chao@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
3 siblings, 0 replies; 27+ messages in thread
From: Chao Li @ 2026-08-18 00:17 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
> On Aug 18, 2026, at 06:20, Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
>>>>
>>>
>>> I don't think the scan can be really unbounded. string_to_uuid()
>>> receives a cstring, so by the time it is called the caller has already
>>> walked or copied the whole string to produce it. So unless the
>>> unbounded scan can be reached in some path I have overlooked, I'd
>>> prefer to keep strlen() here. Happy to change it if you still think it
>>> is worth it.
>>
>> After more thoughts, while I still don't think the scan can be
>> unbounded, using strlen() would add an extra scan just to determine we
>> use hex_decode_safe(). I'll change it to use strnlen() instead.
>
> I've updated the patch accordingly. Please review it.
>
> Regards,
>
> --
> Masahiko Sawada
> Amazon Web Services: https://aws.amazon.com
> <v5-0001-Optimize-UUID-parse-using-SIMD.patch>
Thanks for updating the patch. V5 looks good to me.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-18 00:37 Haibo Yan <tristan.yim@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
3 siblings, 0 replies; 27+ messages in thread
From: Haibo Yan @ 2026-08-18 00:37 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: Chao Li <li.evan.chao@gmail.com>; pgsql-hackers
On Mon, Aug 17, 2026 at 3:21 PM Masahiko Sawada <sawada.mshk@gmail.com>
wrote:
> On Fri, Aug 14, 2026 at 6:08 PM Masahiko Sawada <sawada.mshk@gmail.com>
> wrote:
> >
> > On Fri, Aug 14, 2026 at 4:33 PM Masahiko Sawada <sawada.mshk@gmail.com>
> wrote:
> > >
> > > On Thu, Aug 6, 2026 at 8:07 PM Chao Li <li.evan.chao@gmail.com> wrote:
> > > >
> > > >
> > > >
> > > > > On Aug 7, 2026, at 00:09, Masahiko Sawada <sawada.mshk@gmail.com>
> wrote:
> > > > >
> > > > > On Wed, Aug 5, 2026 at 8:40 PM Chao Li <li.evan.chao@gmail.com>
> wrote:
> > > > >>
> > > > >>
> > > > >>
> > > > >>> On Aug 6, 2026, at 08:19, Masahiko Sawada <sawada.mshk@gmail.com>
> wrote:
> > > > >>>
> > > > >>> On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <
> tristan.yim@gmail.com> wrote:
> > > > >>>>
> > > > >>>> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <
> sawada.mshk@gmail.com> wrote:
> > > > >>>>>
> > > > >>>>> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <
> tristan.yim@gmail.com> wrote:
> > > > >>>>>>
> > > > >>>>>> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <
> sawada.mshk@gmail.com> wrote:
> > > > >>>>>>>
> > > > >>>>>>> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <
> tristan.yim@gmail.com> wrote:
> > > > >>>>>>>>
> > > > >>>>>>>> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <
> sawada.mshk@gmail.com> wrote:
> > > > >>>>>>>>>
> > > > >>>>>>>>> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <
> tristan.yim@gmail.com> wrote:
> > > > >>>>>>>>>>
> > > > >>>>>>>>>>
> > > > >>>>>>>>>>
> > > > >>>>>>>>>> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <
> sawada.mshk@gmail.com> wrote:
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> Hi all,
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> I'd like to propose the $subject.
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> Since commit ec8719ccbfcd made hex_decode_safe()
> SIMD-aware, decoding
> > > > >>>>>>>>>>> a run of hex digits is now fast. The attached patch
> reuses
> > > > >>>>>>>>>>> hex_decode_safe() in the UUID input function to speed up
> parsing.
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> We accept several textual forms of a UUID[1]. The fast
> path handles
> > > > >>>>>>>>>>> the common ones: 32 hex digits, the canonical
> 8x-4x-4x-4x-12x form
> > > > >>>>>>>>>>> (where "nx" means n hex digits), and either of those
> wrapped in
> > > > >>>>>>>>>>> braces. Otherwise, it falls back to the ordinary scalar
> UUID parse.
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> I've benchmarked the parse speed using the following
> query:
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS
> t FROM
> > > > >>>>>>>>>>> generate_series(1, 1000000);
> > > > >>>>>>>>>>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> I compared the execution time of the second query, which
> measures
> > > > >>>>>>>>>>> uuid_in() alone, with/without SIMD optimization. Here
> are results (the
> > > > >>>>>>>>>>> median of 5 runs):
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> HEAD: 208.879 ms
> > > > >>>>>>>>>>> Patched: 40.983 ms
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> The improvements look promising to me. But in a
> realistic pipeline the
> > > > >>>>>>>>>>> parse is a small fraction of the work, so end-to-end
> gains could be
> > > > >>>>>>>>>>> much smaller.
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>>> Feedback is very welcome.
> > > > >>>>>>>>>>>
> > > > >>>>>>>>>> I may be missing something, but I wonder whether the fast
> path is relying on
> > > > >>>>>>>>>> slightly different input semantics from the existing UUID
> parser.
> > > > >>>>>>>>>>
> > > > >>>>>>>>>> In particular, hex_decode_safe() is not a strict “32 hex
> characters only”
> > > > >>>>>>>>>> decoder. It skips whitespace, which is fine for its
> existing callers, but I
> > > > >>>>>>>>>> don’t think UUID input should treat whitespace inside the
> UUID body as
> > > > >>>>>>>>>> ignorable.
> > > > >>>>>>>>>
> > > > >>>>>>>>> Good catch! hex_decode_safe() skips whitespaces so the
> patch accepts
> > > > >>>>>>>>> the following UUID value, which is bad:
> > > > >>>>>>>>>
> > > > >>>>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
> > > > >>>>>>>>> uuid
> > > > >>>>>>>>> --------------------------------------
> > > > >>>>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00
> > > > >>>>>>>>> (1 row)
> > > > >>>>>>>>>
> > > > >>>>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast
> path
> > > > >>>>>>>>>> cannot verify that exactly UUID_LEN bytes were produced.
> > > > >>>>>>>>>
> > > > >>>>>>>>> IIUC hex_decode_safe() does return the output length in
> bytes. So I
> > > > >>>>>>>>> think we can fallback to the scalar UUID parser if
> > > > >>>>>>>>> esctx.error_occurred is true or if the returned value is
> not 16.
> > > > >>>>>>>>>
> > > > >>>>>>>>
> > > > >>>>>>>> You’re right, I misread that part. Checking both
> esctx.error_occurred and
> > > > >>>>>>>> the returned length sounds good to me.
> > > > >>>>>>>>
> > > > >>>>>>>>>>
> > > > >>>>>>>>>> So I think it would be safer either to pre-validate that
> the 32 source
> > > > >>>>>>>>>> characters are all hex digits before calling
> hex_decode_safe(), or to use a
> > > > >>>>>>>>>> UUID-specific strict hex decoder for this path. After
> that, a comment
> > > > >>>>>>>>>> explaining why hex_decode_safe() is safe here would make
> the invariant much
> > > > >>>>>>>>>> clearer.
> > > > >>>>>>>>>
> > > > >>>>>>>>> IIUC hex_decode_simd_helper() accepts only hex digits so
> we could
> > > > >>>>>>>>> re-use it for UUID parsing. Let me check if the above idea
> of using
> > > > >>>>>>>>> the return value works for us first.
> > > > >>>>>>>>>
> > > > >>>>>>>>
> > > > >>>>>>>> That sounds reasonable. My main concern was to keep the
> fast path’s accepted
> > > > >>>>>>>> input set identical to the scalar UUID parser. Falling
> back when the decoded
> > > > >>>>>>>> length is not UUID_LEN, together with regression tests for
> whitespace cases,
> > > > >>>>>>>> should address that.
> > > > >>>>>>>>
> > > > >>>>>>>>>>
> > > > >>>>>>>>>> Could you also add a few regression tests for invalid
> inputs that contain
> > > > >>>>>>>>>> whitespace inside otherwise fast-path-looking UUID
> strings? For example:
> > > > >>>>>>>>>>
> > > > >>>>>>>>>>
> ---------------------------------------------------------------
> > > > >>>>>>>>>>
> > > > >>>>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
> > > > >>>>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
> > > > >>>>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
> > > > >>>>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
> > > > >>>>>>>>>>
> ---------------------------------------------------------------
> > > > >>>>>>>>>>
> > > > >>>>>>>>>> These should continue to be rejected in the same way as
> the scalar parser.
> > > > >>>>>>>>>> Regards,
> > > > >>>>>>>>>
> > > > >>>>>>>>> Agreed.
> > > > >>>>>>>>>
> > > > >>>>>>>
> > > > >>>>>>> I've attached the updated patch.
> > > > >>>>>>>
> > > > >>>>>>> Regards,
> > > > >>>>>>>
> > > > >>>>>>> --
> > > > >>>>>>> Masahiko Sawada
> > > > >>>>>>> Amazon Web Services: https://aws.amazon.com
> > > > >>>>>>
> > > > >>>>>> I noticed a few typos in the comments:
> > > > >>>>>>
> > > > >>>>>> src/backend/utils/adt/uuid.c
> > > > >>>>>> line 56: “scalar implmentation” -> “scalar implementation”
> > > > >>>>>> line 109: “swalled” -> “swallowed”
> > > > >>>>>> line 110: “kepping” -> “keeping”
> > > > >>>>>> line 118: “grammer” -> “grammar”
> > > > >>>>>> line 119: “whitespaces” -> “whitespace”
> > > > >>>>>>
> > > > >>>>>> Could you fix them ?
> > > > >>>>>
> > > > >>>>> Oops, I fixed them and rechecked other places.
> > > > >>>>>
> > > > >>>>> I've attached the updated patch.
> > > > >>>>>
> > > > >>>>> Regards,
> > > > >>>>>
> > > > >>>>> --
> > > > >>>>> Masahiko Sawada
> > > > >>>>> Amazon Web Services: https://aws.amazon.com
> > > > >>>>
> > > > >>>> The code looks good to me now. I only noticed one small typo
> in the
> > > > >>>> commit trailer: Reviwed-by should be Reviewed-by.
> > > > >>>>
> > > > >>>> Otherwise, it looks good. Thank you for fixing these issues.
> > > > >>>>
> > > > >>>
> > > > >>> After spending more time on this patch, I find out two things:
> > > > >>>
> > > > >>> 1. USE_NO_SIMD doesn't work in uuid.c without including
> port/simd.h.
> > > > >>> But including port/simd.h seems wrong as it doesn't use any SIMD
> > > > >>> support functions.
> > > > >>>
> > > > >>> 2. hex_decode_safe() is faster than the current UUID parse
> > > > >>> (isxdigit()+strtoul() approach) even without SIMD. I've created a
> > > > >>> small benchmark test tool (attached as 0002 patch, not intended
> to be
> > > > >>> pushed into the core), and measures UUID parsing performance of
> three
> > > > >>> approaches: 'scalar' is the current string_to_uuid() that uses
> > > > >>> isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD,
> and
> > > > >>> 'nosimd' uses hex_decode_safe() without SIMD, with different
> shapes of
> > > > >>> UUIDs. Here are results:
> > > > >>>
> > > > >>> =# select path, shape, n_inputs, best_ms::numeric(10,3) from
> > > > >>> uuid_parse_bench(100000, 5);
> > > > >>> path | shape | n_inputs | best_ms
> > > > >>> --------+------------------+----------+---------
> > > > >>> scalar | canonical | 100000 | 22.661
> > > > >>> simd | canonical | 100000 | 1.400
> > > > >>> nosimd | canonical | 100000 | 1.652
> > > > >>> scalar | bare32 | 100000 | 15.932
> > > > >>> simd | bare32 | 100000 | 0.471
> > > > >>> nosimd | bare32 | 100000 | 1.110
> > > > >>> scalar | braced_canonical | 100000 | 17.330
> > > > >>> simd | braced_canonical | 100000 | 1.088
> > > > >>> nosimd | braced_canonical | 100000 | 1.314
> > > > >>> scalar | braced_bare32 | 100000 | 15.942
> > > > >>> simd | braced_bare32 | 100000 | 0.488
> > > > >>> nosimd | braced_bare32 | 100000 | 1.141
> > > > >>> scalar | dashed4 | 100000 | 16.185
> > > > >>> simd | dashed4 | 100000 | 16.493
> > > > >>> nosimd | dashed4 | 100000 | 16.403
> > > > >>> scalar | invalid_hex | 100000 | 0.199
> > > > >>> simd | invalid_hex | 100000 | 1.150
> > > > >>> nosimd | invalid_hex | 100000 | 0.385
> > > > >>> (18 rows)
> > > > >>>
> > > > >>> Each of shape means:
> > > > >>> - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
> > > > >>> - 'bare32': 32 contiguous hex digits
> > > > >>> - 'braced_canonical': {8x-4x-4x-4x-12x}
> > > > >>> - 'braced_bare32': {32 hex digits}
> > > > >>> - 'dashed4': dash after every group of 4
> > > > >>> - 'invalid_hdx': canonical but with a invalid digit
> > > > >>>
> > > > >>> 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths
> are
> > > > >>> mostly the same in 'dashed4' and 'invalid_hex' cases because
> 'simd'
> > > > >>> and 'nosimd' fall back to the 'scalar' case. According to these
> > > > >>> results, my conclusion is that we can use hex_decode_safe() for
> > > > >>> canonical forms and 32 contiguous hex forms anyway, and let
> > > > >>> hex_decode_safe() choose whether to use SIMD. We would win in
> either
> > > > >>> case. We still use the current scalar approach for uncommon UUID
> forms
> > > > >>> and error reporting purposes.
> > > > >>>
> > > > >>> Regards,
> > > > >>>
> > > > >>> --
> > > > >>> Masahiko Sawada
> > > > >>> Amazon Web Services: https://aws.amazon.com
> > > > >>>
> <v4-0001-Optimize-UUID-parse-using-SIMD.patch><v4-0002-uuid_parse_bench-module.patch>
> > > > >>
> > > > >> A few comments on v4.
> > > > >>
> > > > >> 1 - 0001
> > > > >> ```
> > > > >> +static void
> > > > >> +string_to_uuid(const char *source, pg_uuid_t *uuid, Node
> *escontext)
> > > > >> +{
> > > > >> + const char *body = source;
> > > > >> + size_t len = strlen(source);
> > > > >> ```
> > > > >>
> > > > >> I think it would be better to avoid strlen(). The old code
> processes at most UUID_LEN (16) byte pairs, so it does not need to scan
> arbitrarily far on malformed input. So, maybe we could use something like
> strnlen(source, 39) instead.
> > > > >
> > > > > While strnlen(source, 39) works there, 39 is a magic number and
> it's
> > > > > tied to the current format check logic. What is the benefit of
> using
> > > > > strnlen(source, 39) instead? I'm not sure it warrants having the
> magic
> > > > > number.
> > > >
> > > > It doesn't have to be exactly 39; 1024 (long enough) would also
> work, or perhaps something based on UUID_LEN, such as UUID_LEN * 3. I think
> the main point is to avoid unbounded scanning on malformed input.
> > > >
> > > > The old code did not have this issue because it only examined as
> much input as needed based on UUID_LEN. The new fast path starts to use
> strlen(), so this would be a new risk introduced by the optimization.
> > >
> > > I don't think the scan can be really unbounded. string_to_uuid()
> > > receives a cstring, so by the time it is called the caller has already
> > > walked or copied the whole string to produce it. So unless the
> > > unbounded scan can be reached in some path I have overlooked, I'd
> > > prefer to keep strlen() here. Happy to change it if you still think it
> > > is worth it.
> >
> > After more thoughts, while I still don't think the scan can be
> > unbounded, using strlen() would add an extra scan just to determine we
> > use hex_decode_safe(). I'll change it to use strnlen() instead.
>
> I've updated the patch accordingly. Please review it.
>
The code looks good to me. Just one tiny thing: you stated that
The fast path is deliberately not conditional on SIMD support
but the test is still commented as:
Test UUID shapes that the parser uses the SIMD path.
Maybe it could be changed to:
Test UUID shapes handled by the fast path.
Thanks,
Haibo
>
> Regards,
>
> --
> Masahiko Sawada
> Amazon Web Services: https://aws.amazon.com
>
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-18 08:53 John Naylor <johncnaylorls@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
3 siblings, 1 reply; 27+ messages in thread
From: John Naylor @ 2026-08-18 08:53 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: Chao Li <li.evan.chao@gmail.com>; Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
On Tue, Aug 18, 2026 at 5:21 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> I've updated the patch accordingly. Please review it.
+ * Note that this fast path is not conditional on SIMD support:
+ * hex_decode_safe() picks a vectorized or scalar implementation itself, and
+ * even its scalar implementation is far faster than string_to_uuid_scalar().
This doesn't seem like relevant info at the interface level -- I'd
just leave it out. The previous paragraph already states that it calls
out to hex_decode_safe() when possible. It's also confusing to later
read:
+ * Decode the UUID hex data using our hex decoder that is SIMD-aware. We
The relevant info at this point is not how hex_decode_safe() works --
that's internal to that function and the caller doesn't (and
shouldn't) need to know about it. It might be relevant to say that if
we've gotten this far the fast path is likely to be successful. That
then logically leads in to the mention of error handling. Speaking of,
'escontext' and 'esctx' are confusingly similar -- it should be
obvious from the name what the purpose of the private one is. Maybe
'scratch' or 'private' as a prefix.
--
John Naylor
Amazon Web Services
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-08-18 20:13 Masahiko Sawada <sawada.mshk@gmail.com>
parent: John Naylor <johncnaylorls@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Masahiko Sawada @ 2026-08-18 20:13 UTC (permalink / raw)
To: John Naylor <johncnaylorls@gmail.com>; +Cc: Chao Li <li.evan.chao@gmail.com>; Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
On Tue, Aug 18, 2026 at 1:53 AM John Naylor <johncnaylorls@gmail.com> wrote:
>
> On Tue, Aug 18, 2026 at 5:21 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > I've updated the patch accordingly. Please review it.
>
> + * Note that this fast path is not conditional on SIMD support:
> + * hex_decode_safe() picks a vectorized or scalar implementation itself, and
> + * even its scalar implementation is far faster than string_to_uuid_scalar().
>
> This doesn't seem like relevant info at the interface level -- I'd
> just leave it out. The previous paragraph already states that it calls
> out to hex_decode_safe() when possible. It's also confusing to later
> read:
>
> + * Decode the UUID hex data using our hex decoder that is SIMD-aware. We
>
> The relevant info at this point is not how hex_decode_safe() works --
> that's internal to that function and the caller doesn't (and
> shouldn't) need to know about it. It might be relevant to say that if
> we've gotten this far the fast path is likely to be successful. That
> then logically leads in to the mention of error handling. Speaking of,
> 'escontext' and 'esctx' are confusingly similar -- it should be
> obvious from the name what the purpose of the private one is. Maybe
> 'scratch' or 'private' as a prefix.
Thank you for reviewing the patch! I agree with all the above comments.
I've attached the updated patch.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-patch] v6-0001-Use-hex_decode_safe-to-speed-up-UUID-input.patch (12.5K, ../../CAD21AoD0nDmPqPd8mi8KK173R9Mcg2oTj7W6THM4bLRrvJ+HDw@mail.gmail.com/2-v6-0001-Use-hex_decode_safe-to-speed-up-UUID-input.patch)
download | inline diff:
From d2bfc203ff92d1cce7ea10a27c86f6d42162a842 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 25 Jun 2026 10:03:44 -0700
Subject: [PATCH v6 1/2] Use hex_decode_safe() to speed up UUID input.
Previously, string_to_uuid() decoded one byte at a time, calling
isxdigit() twice and strtoul() once for every pair of hexadecimal
digits. That loop dominated the cost of uuid_in().
Commit ec8719ccbfcd made hex_decode_safe() decode a run of hexadecimal
digits in bulk, so this commit adds a fast path for the two common
shapes: a bare string of 32 hexadecimal digits, and the canonical
8x-4x-4x-4x-12x form (where "nx" means n hexadecimal digits), each
optionally wrapped in braces. Both are compacted into 32 contiguous
hexadecimal digits and decoded with hex_decode_safe(). Any other
shape, or any decoding error, is handed off to the original
byte-at-a-time parser, now string_to_uuid_scalar(), so the accepted
grammar and the error messages are unchanged.
hex_decode_safe() silently skips whitespace while the UUID grammar
does not, so a decode can succeed and still write fewer than UUID_LEN
bytes. The fast path therefore treats a short result as a failure,
just like an error, and lets the scalar parser reject the input and
report the syntax error.
Rejecting an input that has one of the fast-path shapes now costs a
second parse, since the error is reported by string_to_uuid_scalar()
rather than by the failed decode. That keeps the message independent
of which path rejected the input. Callers that throw absorb the extra
parse in the error path, and callers that use a soft error context,
such as pg_input_is_valid(), pay only a few nanoseconds per value.
The fast path is deliberately not conditional on SIMD
support. hex_decode_safe() selects a vectorized or scalar
implementation itself, and even its scalar implementation is an order
of magnitude faster than decoding a byte at a time, so gating this on
USE_NO_SIMD would only penalize platforms that have neither SSE2 nor
NEON.
Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: John Naylor <johncnaylorls@gmail.com>
Discussion: https://postgr.es/m/CAD21AoCqeR4UQU77Q_yOMNNzJ7AVeiO5QZT+4HnzPm4Wm-e02Q@mail.gmail.com
---
src/backend/utils/adt/uuid.c | 101 +++++++++++++++++++++++++++--
src/test/regress/expected/uuid.out | 75 +++++++++++++++++++++
src/test/regress/sql/uuid.sql | 25 +++++++
3 files changed, 196 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 28e18940a9d..c019e17f21b 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -19,7 +19,9 @@
#include "common/hashfn.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
#include "port/pg_bswap.h"
+#include "utils/builtins.h"
#include "utils/fmgrprotos.h"
#include "utils/guc.h"
#include "utils/skipsupport.h"
@@ -139,13 +141,13 @@ uuid_out(PG_FUNCTION_ARGS)
}
/*
- * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
- * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
- * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
- * digits, is the only one used for output.)
+ * Reference implementation of the UUID grammar, parsing one byte at a time.
+ * string_to_uuid() recognizes the common shapes more cheaply and defers to
+ * this function for everything else, so this is also the only place that
+ * reports a syntax error.
*/
static void
-string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+string_to_uuid_scalar(const char *source, pg_uuid_t *uuid, Node *escontext)
{
const char *src = source;
bool braces = false;
@@ -194,6 +196,95 @@ syntax_error:
"uuid", source)));
}
+/*
+ * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
+ * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
+ * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
+ * digits, is the only one used for output.)
+ *
+ * The two common shapes -- a bare string of 32 hexadecimal digits and the
+ * canonical form, each optionally wrapped in braces -- are compacted into 32
+ * contiguous hex digits and decoded with hex_decode_safe(), which is much
+ * faster than the byte-at-a-time loop. Any other shape, or any decoding
+ * error, is handed off to string_to_uuid_scalar() so that the accepted
+ * grammar and the error messages are unchanged.
+ */
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+ const char *body = source;
+ const char *hexsrc = NULL;
+ char hexbuf[32];
+ uint64 written;
+ size_t len;
+ ErrorSaveContext private_escontext = {T_ErrorSaveContext};
+
+ /*
+ * Measure the input only far enough to classify its shape. The bound must
+ * exceed the longest shape handled here, the braced canonical form at 38
+ * characters: strnlen() returns the bound for anything at least that
+ * long, so stopping at an accepted length would accept a longer string
+ * that merely starts with a valid UUID.
+ */
+ len = strnlen(source, 64);
+
+ /* Strip one optional surrounding brace pair */
+ if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
+ {
+ body = source + 1;
+ len -= 2;
+ }
+
+ if (len == 32)
+ {
+ /*
+ * Body is already 32 contiguous hex digits -- decode straight from
+ * the input. hex_decode_safe() reads exactly body[0..31], so it never
+ * touches the trailing NUL or '}'.
+ */
+ hexsrc = body;
+ }
+ else if (len == 36 && body[8] == '-' && body[13] == '-' &&
+ body[18] == '-' && body[23] == '-')
+ {
+ /*
+ * Canonical 8x-4x-4x-4x-12x form; compact them into hexbuf with
+ * fixed-offset copies, dropping the dashes.
+ */
+ memcpy(&hexbuf[0], &body[0], 8);
+ memcpy(&hexbuf[8], &body[9], 4);
+ memcpy(&hexbuf[12], &body[14], 4);
+ memcpy(&hexbuf[16], &body[19], 4);
+ memcpy(&hexbuf[20], &body[24], 12);
+ hexsrc = hexbuf;
+ }
+
+ if (hexsrc == NULL)
+ {
+ /* Uncommon shape; let the general parse handle it */
+ string_to_uuid_scalar(source, uuid, escontext);
+ return;
+ }
+
+ /*
+ * The shape matched, so the decode is expected to succeed. Any error is
+ * routed into a private context and discarded, leaving
+ * string_to_uuid_scalar() to parse the input again and report the syntax
+ * error, so that the message does not depend on which path rejected the
+ * input.
+ */
+ written = hex_decode_safe(hexsrc, 32, (char *) uuid->data,
+ (Node *) &private_escontext);
+
+ /*
+ * A short result must be rejected as well as an error: hex_decode_safe()
+ * skips whitespace, so it can succeed yet write fewer than UUID_LEN
+ * bytes, whereas the UUID grammar forbids whitespace.
+ */
+ if (private_escontext.error_occurred || written != UUID_LEN)
+ string_to_uuid_scalar(source, uuid, escontext);
+}
+
Datum
uuid_recv(PG_FUNCTION_ARGS)
{
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index d542eb14b26..6e35f439c68 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -375,5 +375,80 @@ SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
t
(1 row)
+-- Test UUID shapes that the parser uses the fast path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+ uuid
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+-- Test if the UUID parser using the fast path correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f32 99c6770"
+LINE 1: SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+ ^
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c67 "
+LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+ ^
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: " 35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR: invalid input syntax for type uuid: "AZ35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ ^
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a-7143-4912-9b55-f322699c6770}"
+LINE 1: SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c6770}"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ ^
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ERROR: invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c67 }"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+ ^
+-- The parser only measures the input far enough to classify its shape. If it
+-- stopped measuring at one of the accepted lengths, a longer string that
+-- merely starts with a valid UUID would look like that UUID and be accepted
+-- with the rest silently ignored, so check that trailing data is rejected.
+SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a714349129b55f322699c6770TRAILING"
+LINE 1: SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
+ ^
+SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
+ERROR: invalid input syntax for type uuid: "{5b35380a714349129b55f322699c6770}TRAILING"
+LINE 1: SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
+ ^
+SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
+ERROR: invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c6770TRAILING"
+LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
+ ^
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uuid;
+ERROR: invalid input syntax for type uuid: "{5b35380a-7143-4912-9b55-f322699c6770}TRAILING"
+LINE 1: SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uui...
+ ^
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
index 54f0d8f8255..0719700fda3 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -178,5 +178,30 @@ SELECT '\x019a2f859ced7225b99d9c55044a2563'::bytea::uuid;
SELECT '\x1234567890abcdef'::bytea::uuid; -- error
SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
+-- Test UUID shapes that the parser uses the fast path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+
+-- Test if the UUID parser using the fast path correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32 99c6770'::uuid;
+SELECT '5b35380a-7143-4912-9b55-f322699c67 '::uuid;
+SELECT ' 35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c67 }'::uuid;
+
+-- The parser only measures the input far enough to classify its shape. If it
+-- stopped measuring at one of the accepted lengths, a longer string that
+-- merely starts with a valid UUID would look like that UUID and be accepted
+-- with the rest silently ignored, so check that trailing data is rejected.
+SELECT '5b35380a714349129b55f322699c6770TRAILING'::uuid;
+SELECT '{5b35380a714349129b55f322699c6770}TRAILING'::uuid;
+SELECT '5b35380a-7143-4912-9b55-f322699c6770TRAILING'::uuid;
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}TRAILING'::uuid;
+
-- clean up
DROP TABLE guid1, guid2, guid3 CASCADE;
--
2.55.0
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-09-09 20:45 Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Bharath Rupireddy @ 2026-09-09 20:45 UTC (permalink / raw)
To: Masahiko Sawada <sawada.mshk@gmail.com>; +Cc: John Naylor <johncnaylorls@gmail.com>; Chao Li <li.evan.chao@gmail.com>; Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
Hi,
On Tue, Aug 18, 2026 at 1:14 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> I've attached the updated patch.
I looked at the v6 patch and the diff (comments and commit message)
from that to the v5 patch looks good to me. pgindent and make check
are happy. I marked the CF entry RfC.
--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-09-14 21:00 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
0 siblings, 1 reply; 27+ messages in thread
From: Masahiko Sawada @ 2026-09-14 21:00 UTC (permalink / raw)
To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: John Naylor <johncnaylorls@gmail.com>; Chao Li <li.evan.chao@gmail.com>; Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
On Wed, Sep 9, 2026 at 1:45 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
>
> Hi,
>
> On Tue, Aug 18, 2026 at 1:14 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> >
> > I've attached the updated patch.
>
> I looked at the v6 patch and the diff (comments and commit message)
> from that to the v5 patch looks good to me. pgindent and make check
> are happy. I marked the CF entry RfC.
Thank you for reviewing the patch! I'm going to push it, barring any objections.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
* Re: Optimize UUID parse using SIMD
@ 2026-09-16 21:20 Masahiko Sawada <sawada.mshk@gmail.com>
parent: Masahiko Sawada <sawada.mshk@gmail.com>
0 siblings, 0 replies; 27+ messages in thread
From: Masahiko Sawada @ 2026-09-16 21:20 UTC (permalink / raw)
To: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>; +Cc: John Naylor <johncnaylorls@gmail.com>; Chao Li <li.evan.chao@gmail.com>; Haibo Yan <tristan.yim@gmail.com>; pgsql-hackers
On Mon, Sep 14, 2026 at 2:00 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>
> On Wed, Sep 9, 2026 at 1:45 PM Bharath Rupireddy
> <bharath.rupireddyforpostgres@gmail.com> wrote:
> >
> > Hi,
> >
> > On Tue, Aug 18, 2026 at 1:14 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
> > >
> > > I've attached the updated patch.
> >
> > I looked at the v6 patch and the diff (comments and commit message)
> > from that to the v5 patch looks good to me. pgindent and make check
> > are happy. I marked the CF entry RfC.
>
> Thank you for reviewing the patch! I'm going to push it, barring any objections.
Pushed.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 27+ messages in thread
end of thread, other threads:[~2026-09-16 21:20 UTC | newest]
Thread overview: 27+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-06-25 18:27 Optimize UUID parse using SIMD Masahiko Sawada <sawada.mshk@gmail.com>
2026-06-25 19:30 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-06-29 21:54 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-06-25 21:31 ` Haibo Yan <tristan.yim@gmail.com>
2026-06-25 22:16 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-06-29 02:20 ` Haibo Yan <tristan.yim@gmail.com>
2026-06-29 21:55 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-06-30 00:53 ` Haibo Yan <tristan.yim@gmail.com>
2026-06-30 17:53 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-06-30 18:03 ` Haibo Yan <tristan.yim@gmail.com>
2026-08-06 00:19 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-08-06 03:40 ` Chao Li <li.evan.chao@gmail.com>
2026-08-06 16:09 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-08-07 03:07 ` Chao Li <li.evan.chao@gmail.com>
2026-08-14 23:33 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-08-15 01:08 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-08-16 00:59 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-08-17 22:20 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-08-17 23:45 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-08-18 00:17 ` Chao Li <li.evan.chao@gmail.com>
2026-08-18 00:37 ` Haibo Yan <tristan.yim@gmail.com>
2026-08-18 08:53 ` John Naylor <johncnaylorls@gmail.com>
2026-08-18 20:13 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-09-09 20:45 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
2026-09-14 21:00 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-09-16 21:20 ` Masahiko Sawada <sawada.mshk@gmail.com>
2026-08-06 21:42 ` Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox