| From: | Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com> |
|---|---|
| To: | Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com> |
| Cc: | Haibo Yan <tristan(dot)yim(at)gmail(dot)com>, PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org> |
| Subject: | Re: Optimize UUID parse using SIMD |
| Date: | 2026-08-06 03:40:05 |
| Message-ID: | 93F6B685-C5DC-444D-A92D-0927A528C1B8@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
> On Aug 6, 2026, at 08:19, Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com> wrote:
>
> On Tue, Jun 30, 2026 at 11:03 AM Haibo Yan <tristan(dot)yim(at)gmail(dot)com> wrote:
>>
>> On Tue, Jun 30, 2026 at 10:53 AM Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com> wrote:
>>>
>>> On Mon, Jun 29, 2026 at 5:53 PM Haibo Yan <tristan(dot)yim(at)gmail(dot)com> wrote:
>>>>
>>>> On Mon, Jun 29, 2026 at 2:55 PM Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com> wrote:
>>>>>
>>>>> On Sun, Jun 28, 2026 at 7:20 PM Haibo Yan <tristan(dot)yim(at)gmail(dot)com> wrote:
>>>>>>
>>>>>> On Thu, Jun 25, 2026 at 3:16 PM Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com> wrote:
>>>>>>>
>>>>>>> On Thu, Jun 25, 2026 at 2:31 PM Haibo Yan <tristan(dot)yim(at)gmail(dot)com> wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On Thu, Jun 25, 2026 at 11:28 AM Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com> wrote:
>>>>>>>>>
>>>>>>>>> Hi all,
>>>>>>>>>
>>>>>>>>> I'd like to propose the $subject.
>>>>>>>>>
>>>>>>>>> Since commit ec8719ccbfcd made hex_decode_safe() SIMD-aware, decoding
>>>>>>>>> a run of hex digits is now fast. The attached patch reuses
>>>>>>>>> hex_decode_safe() in the UUID input function to speed up parsing.
>>>>>>>>>
>>>>>>>>> We accept several textual forms of a UUID[1]. The fast path handles
>>>>>>>>> the common ones: 32 hex digits, the canonical 8x-4x-4x-4x-12x form
>>>>>>>>> (where "nx" means n hex digits), and either of those wrapped in
>>>>>>>>> braces. Otherwise, it falls back to the ordinary scalar UUID parse.
>>>>>>>>>
>>>>>>>>> I've benchmarked the parse speed using the following query:
>>>>>>>>>
>>>>>>>>> CREATE TEMP TABLE u AS SELECT gen_random_uuid()::text AS t FROM
>>>>>>>>> generate_series(1, 1000000);
>>>>>>>>> EXPLAIN (ANALYZE, TIMING OFF) SELECT t::uuid FROM u;
>>>>>>>>>
>>>>>>>>> I compared the execution time of the second query, which measures
>>>>>>>>> uuid_in() alone, with/without SIMD optimization. Here are results (the
>>>>>>>>> median of 5 runs):
>>>>>>>>>
>>>>>>>>> HEAD: 208.879 ms
>>>>>>>>> Patched: 40.983 ms
>>>>>>>>>
>>>>>>>>> The improvements look promising to me. But in a realistic pipeline the
>>>>>>>>> parse is a small fraction of the work, so end-to-end gains could be
>>>>>>>>> much smaller.
>>>>>>>>>
>>>>>>>>> Feedback is very welcome.
>>>>>>>>>
>>>>>>>> I may be missing something, but I wonder whether the fast path is relying on
>>>>>>>> slightly different input semantics from the existing UUID parser.
>>>>>>>>
>>>>>>>> In particular, hex_decode_safe() is not a strict “32 hex characters only”
>>>>>>>> decoder. It skips whitespace, which is fine for its existing callers, but I
>>>>>>>> don’t think UUID input should treat whitespace inside the UUID body as
>>>>>>>> ignorable.
>>>>>>>
>>>>>>> Good catch! hex_decode_safe() skips whitespaces so the patch accepts
>>>>>>> the following UUID value, which is bad:
>>>>>>>
>>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid;
>>>>>>> uuid
>>>>>>> --------------------------------------
>>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00
>>>>>>> (1 row)
>>>>>>>
>>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast path
>>>>>>>> cannot verify that exactly UUID_LEN bytes were produced.
>>>>>>>
>>>>>>> IIUC hex_decode_safe() does return the output length in bytes. So I
>>>>>>> think we can fallback to the scalar UUID parser if
>>>>>>> esctx.error_occurred is true or if the returned value is not 16.
>>>>>>>
>>>>>>
>>>>>> You’re right, I misread that part. Checking both esctx.error_occurred and
>>>>>> the returned length sounds good to me.
>>>>>>
>>>>>>>>
>>>>>>>> So I think it would be safer either to pre-validate that the 32 source
>>>>>>>> characters are all hex digits before calling hex_decode_safe(), or to use a
>>>>>>>> UUID-specific strict hex decoder for this path. After that, a comment
>>>>>>>> explaining why hex_decode_safe() is safe here would make the invariant much
>>>>>>>> clearer.
>>>>>>>
>>>>>>> IIUC hex_decode_simd_helper() accepts only hex digits so we could
>>>>>>> re-use it for UUID parsing. Let me check if the above idea of using
>>>>>>> the return value works for us first.
>>>>>>>
>>>>>>
>>>>>> That sounds reasonable. My main concern was to keep the fast path’s accepted
>>>>>> input set identical to the scalar UUID parser. Falling back when the decoded
>>>>>> length is not UUID_LEN, together with regression tests for whitespace cases,
>>>>>> should address that.
>>>>>>
>>>>>>>>
>>>>>>>> Could you also add a few regression tests for invalid inputs that contain
>>>>>>>> whitespace inside otherwise fast-path-looking UUID strings? For example:
>>>>>>>>
>>>>>>>> ---------------------------------------------------------------
>>>>>>>>
>>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid;
>>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid;
>>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid;
>>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid;
>>>>>>>> ---------------------------------------------------------------
>>>>>>>>
>>>>>>>> These should continue to be rejected in the same way as the scalar parser.
>>>>>>>> Regards,
>>>>>>>
>>>>>>> Agreed.
>>>>>>>
>>>>>
>>>>> I've attached the updated patch.
>>>>>
>>>>> Regards,
>>>>>
>>>>> --
>>>>> Masahiko Sawada
>>>>> Amazon Web Services: https://aws.amazon.com
>>>>
>>>> I noticed a few typos in the comments:
>>>>
>>>> src/backend/utils/adt/uuid.c
>>>> line 56: “scalar implmentation” -> “scalar implementation”
>>>> line 109: “swalled” -> “swallowed”
>>>> line 110: “kepping” -> “keeping”
>>>> line 118: “grammer” -> “grammar”
>>>> line 119: “whitespaces” -> “whitespace”
>>>>
>>>> Could you fix them ?
>>>
>>> Oops, I fixed them and rechecked other places.
>>>
>>> I've attached the updated patch.
>>>
>>> Regards,
>>>
>>> --
>>> Masahiko Sawada
>>> Amazon Web Services: https://aws.amazon.com
>>
>> The code looks good to me now. I only noticed one small typo in the
>> commit trailer: Reviwed-by should be Reviewed-by.
>>
>> Otherwise, it looks good. Thank you for fixing these issues.
>>
>
> After spending more time on this patch, I find out two things:
>
> 1. USE_NO_SIMD doesn't work in uuid.c without including port/simd.h.
> But including port/simd.h seems wrong as it doesn't use any SIMD
> support functions.
>
> 2. hex_decode_safe() is faster than the current UUID parse
> (isxdigit()+strtoul() approach) even without SIMD. I've created a
> small benchmark test tool (attached as 0002 patch, not intended to be
> pushed into the core), and measures UUID parsing performance of three
> approaches: 'scalar' is the current string_to_uuid() that uses
> isxdigit()+strtoul()), 'simd' uses hex_decode_safe() with SIMD, and
> 'nosimd' uses hex_decode_safe() without SIMD, with different shapes of
> UUIDs. Here are results:
>
> =# select path, shape, n_inputs, best_ms::numeric(10,3) from
> uuid_parse_bench(100000, 5);
> path | shape | n_inputs | best_ms
> --------+------------------+----------+---------
> scalar | canonical | 100000 | 22.661
> simd | canonical | 100000 | 1.400
> nosimd | canonical | 100000 | 1.652
> scalar | bare32 | 100000 | 15.932
> simd | bare32 | 100000 | 0.471
> nosimd | bare32 | 100000 | 1.110
> scalar | braced_canonical | 100000 | 17.330
> simd | braced_canonical | 100000 | 1.088
> nosimd | braced_canonical | 100000 | 1.314
> scalar | braced_bare32 | 100000 | 15.942
> simd | braced_bare32 | 100000 | 0.488
> nosimd | braced_bare32 | 100000 | 1.141
> scalar | dashed4 | 100000 | 16.185
> simd | dashed4 | 100000 | 16.493
> nosimd | dashed4 | 100000 | 16.403
> scalar | invalid_hex | 100000 | 0.199
> simd | invalid_hex | 100000 | 1.150
> nosimd | invalid_hex | 100000 | 0.385
> (18 rows)
>
> Each of shape means:
> - 'canonical': 8x-4x-4x-4x-12x, what uuid_out() emits
> - 'bare32': 32 contiguous hex digits
> - 'braced_canonical': {8x-4x-4x-4x-12x}
> - 'braced_bare32': {32 hex digits}
> - 'dashed4': dash after every group of 4
> - 'invalid_hdx': canonical but with a invalid digit
>
> 'nosimd' is 10x~ faster than 'scalar' in most cases. All paths are
> mostly the same in 'dashed4' and 'invalid_hex' cases because 'simd'
> and 'nosimd' fall back to the 'scalar' case. According to these
> results, my conclusion is that we can use hex_decode_safe() for
> canonical forms and 32 contiguous hex forms anyway, and let
> hex_decode_safe() choose whether to use SIMD. We would win in either
> case. We still use the current scalar approach for uncommon UUID forms
> and error reporting purposes.
>
> Regards,
>
> --
> Masahiko Sawada
> Amazon Web Services: https://aws.amazon.com
> <v4-0001-Optimize-UUID-parse-using-SIMD.patch><v4-0002-uuid_parse_bench-module.patch>
A few comments on v4.
1 - 0001
```
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+ const char *body = source;
+ size_t len = strlen(source);
```
I think it would be better to avoid strlen(). The old code processes at most UUID_LEN (16) byte pairs, so it does not need to scan arbitrarily far on malformed input. So, maybe we could use something like strnlen(source, 39) instead.
2 - 0002 - uuid_parse_bench/Makefile
```
+REGRESS = uuid_parse_bench
```
When I tried to run the test, I got an error:
```
# +++ regress check in src/test/modules/uuid_parse_bench +++
# initializing database system by copying initdb template
# using temp instance on port 52544 with PID 56970
/bin/sh: /Users/chaol/Documents/code/postgresql/src/test/modules/uuid_parse_bench/sql/uuid_parse_bench.sql: No such file or directory
diff: /Users/chaol/Documents/code/postgresql/src/test/modules/uuid_parse_bench/expected/uuid_parse_bench.out: No such file or directory
```
Not sure if you missed to check in uuid_parse_bench.sql and uuid_parse_bench.out.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
| From | Date | Subject | |
|---|---|---|---|
| Next Message | shveta malik | 2026-08-06 03:46:28 | Re: [PATCH] Release replication slot on error in SQL-callable slot functions |
| Previous Message | Xuneng Zhou | 2026-08-06 03:34:14 | Re: BUG: ReadStream look-ahead exhausts local buffers when effective_io_concurrency>=64 |