Optimize UUID parse using SIMD
Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.
You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:
docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t248641psql -h localhost -U postgresBuilt from patchset v19 (message #19), August 17, 2026 at 10:34 PM.
Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:
git clone --branch t248641_19 https://github.com/hackorum-dev/postgres.gitIn a checkout you already have, add the fork once:
git remote add hackorum https://github.com/hackorum-dev/postgres.gitthen, for this patchset and every later one:
git fetch hackorum t248641_19 && git checkout t248641_19Patchset v19 (message #19) is on t248641_19
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]https://www.postgresql.org/docs/devel/datatype-uuid.html#DATATYPE-UUID. 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:
v1-0001-Optimize-UUID-parse-using-SIMD.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Optimize-UUID-parse-using-SIMD.patchDownload+87-6
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
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 msThe 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
Show quoted text
Regards,
[1] https://www.postgresql.org/docs/devel/datatype-uuid.html#DATATYPE-UUID
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
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 msThe 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
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 msThe 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
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 msNice!
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() toInstead 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
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 msThe 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:
v2-0001-Optimize-UUID-parse-using-SIMD.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Optimize-UUID-parse-using-SIMD.patchDownload+168-6
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 msThe 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
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 msThe 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.comI 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
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 msThe 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.comI 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
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 msThe 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.comI 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.comThe 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:
t248641_11v4-0001-Optimize-UUID-parse-using-SIMD.patchtext/x-patch; charset=US-ASCII; name=v4-0001-Optimize-UUID-parse-using-SIMD.patchDownload+160-6
v4-0002-uuid_parse_bench-module.patchtext/x-patch; charset=US-ASCII; name=v4-0002-uuid_parse_bench-module.patchDownload+726-1
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 msThe 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.comI 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.comThe 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/
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 msThe 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.comI 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.comThe 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
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
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 msThe 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.comI 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.comThe 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/
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 msThe 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.comI 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.comThe 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
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 msThe 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.comI 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.comThe 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
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
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 msThe 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.comI 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.comThe 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:
t248641_19v5-0001-Optimize-UUID-parse-using-SIMD.patchtext/x-patch; charset=US-ASCII; name=v5-0001-Optimize-UUID-parse-using-SIMD.patchDownload+198-6
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