| From: | Bharath Rupireddy <bharath(dot)rupireddyforpostgres(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 21:42:29 |
| Message-ID: | CALj2ACW_XHVm+-a3HEnRU63ft9=9MdMDUno-s1EKxHVzp6pVNQ@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi,
On Wed, Aug 5, 2026 at 5:20 PM Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)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
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Daniel Gustafsson | 2026-08-06 21:45:29 | Re: [PATCH] Remove unused scram_client_key_len and scram_server_key_len fields |
| Previous Message | Nathan Bossart | 2026-08-06 21:38:28 | Re: problems with toast.* reloptions |