Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wsAwC-000RER-28 for pgsql-hackers@arkaria.postgresql.org; Fri, 07 Aug 2026 03:08:00 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wsAwB-0073Bp-2w for pgsql-hackers@arkaria.postgresql.org; Fri, 07 Aug 2026 03:07:59 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wsAwB-0073Bh-1K for pgsql-hackers@lists.postgresql.org; Fri, 07 Aug 2026 03:07:59 +0000 Received: from mail-pj1-x1030.google.com ([2607:f8b0:4864:20::1030]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wsAw8-00000000cMZ-24pD for pgsql-hackers@postgresql.org; Fri, 07 Aug 2026 03:07:58 +0000 Received: by mail-pj1-x1030.google.com with SMTP id 98e67ed59e1d1-38e347638adso3273076a91.0 for ; Thu, 06 Aug 2026 20:07:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786072075; x=1786676875; darn=postgresql.org; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:content-type:from:to:cc :subject:date:message-id:reply-to:content-type; bh=mfaO7Q8JV6VmWSSl71W5++rwXYc1zZJ1XAVOEDNyKOA=; b=bNLpiJRXAY9Zzun5jbMPXKfWMe2R8Xyls9AxTb34ypV6ysog82nyBUOobiruHRB3Pe bIUXesxOKvdUBAErgunHvbMEO4LXYD5VHWB+POKp73s7JH8qy8wnKLkewWsmYZHzbiNP nrQjFc4Jl2WsHFlN8FbyBVHabxwSMENWNn7uj05RiRUjwk6q16eWrOB4yRU31oubaB6O gnM4t17kWmBPpX5WumQVyDQ5xauU2TCKflpOKX1CIE3KSCBeAkG46+gNvuWqmBGLRbKt DDBJEQqF1v9Ta8UP7AJAJGo5dMpBysWr0yIgYEFvvv7EMTpJtmt9/d2MKex6qIJc+INr koIQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786072075; x=1786676875; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:content-type:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=mfaO7Q8JV6VmWSSl71W5++rwXYc1zZJ1XAVOEDNyKOA=; b=lKzu+puHmerwY+ee1Vo9WjRGJWJAgnIJJvnGwFPem0UtKDGCzQspEr1CugqbsZet3y m/tkOifrb3MsyTDekOjoMI0qOsc4/x0eaIRz2mcT6+W745XlebSB3NBiaFRIceXn2c1a KtENviQ7EI2uVzjzLchEvC4b8yUELp/ZKicXZahgbTjOXt1dXUSfaIXaC7ZKtBJdliZu FKjfoEwZTOWe9I8cBPhd9d2oln3/FDrQ5GVofOeW9zTKGyyREk9AtmV5corYh6mHYN/M gP3f8jKm3hxGiMLkDpY5ealGhT38TaD6Qsl1yVZyrWoJmKOtpd4uvUjxy5psShMiVQoq y7MA== X-Forwarded-Encrypted: i=1; AHgh+RolXn7Y94odwtEHVr7TuceGM4wmrwPpQT4WCL/8J70SpVzyvJ+yQ9HNjeNYf2cIm8az4IBfhXedeFidj0fp@postgresql.org X-Gm-Message-State: AOJu0YxaeE+VAo1jvCUmyBbl0QAA66y9vnd2dSdxCENJ84L2WiQBbU10 cQMauZDz3ijuhB7e3qQxmINMxk5GRT4JrOQZkgT9QqBz3Fa3Y8JNtSoq X-Gm-Gg: AR+sD11QFVU7KwrnrGbJNBqF3OT1adWgIspOrroDK7Ar00gkbTLLxl0dLwsl3CTjpo3 0XvveMcTL0CXsPb6ilDqfnxG+t+HoXc2SfrWQ+wz3XSlpxgdtOrFWY7HsdDikUMbW8EAk0GBJA0 /GcuBWJm13r1lUHWHXg7t41IrSl+djjNux3D3IIXngjaY8MlLi01E9XVeF4lDKddInwA7sa+U9O r/o4Lrm4GlzVeay75Hy55KlXAikUX4/vbQv0EZ/4g6VNZrsdMiPCfTJgvNyyhQRcPywwr/LIrCA hQ8GQemsg6sBYDOi4p9drXn5uMzdCA/M6FVu0y9HeruUoVcntieT/iyNt+U20GaemO7szoid0GR fwRxG3Xptzwtc77cURgbUeyo96NC2QyIf5N/3bljjlAWJi53MKrcKFeH0Rc1hSur5PNUiwEpCeJ 5Uj3hXL19iNPTPOtR283fU86mTut6NHfLlXgdGErFc84gMYth/La+jl/9ppqP1OS03Fn3GMA== X-Received: by 2002:a17:90b:4c45:b0:38f:dec8:f7e9 with SMTP id 98e67ed59e1d1-3903c5a79f7mr20201030a91.12.1786072075301; Thu, 06 Aug 2026 20:07:55 -0700 (PDT) Received: from smtpclient.apple ([185.135.79.161]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-39085f2b2fdsm2851392a91.12.2026.08.06.20.07.51 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 06 Aug 2026 20:07:53 -0700 (PDT) Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.600.51.1.1\)) Subject: Re: Optimize UUID parse using SIMD From: Chao Li In-Reply-To: Date: Fri, 7 Aug 2026 11:07:17 +0800 Cc: Haibo Yan , PostgreSQL-development Content-Transfer-Encoding: quoted-printable Message-Id: <5A6326C9-25EC-4D07-99C2-DA490A7BC7ED@gmail.com> References: <93F6B685-C5DC-444D-A92D-0927A528C1B8@gmail.com> To: Masahiko Sawada X-Mailer: Apple Mail (2.3864.600.51.1.1) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk > On Aug 7, 2026, at 00:09, Masahiko Sawada = wrote: >=20 > On Wed, Aug 5, 2026 at 8:40=E2=80=AFPM Chao Li = wrote: >>=20 >>=20 >>=20 >>> On Aug 6, 2026, at 08:19, Masahiko Sawada = wrote: >>>=20 >>> On Tue, Jun 30, 2026 at 11:03=E2=80=AFAM Haibo Yan = wrote: >>>>=20 >>>> On Tue, Jun 30, 2026 at 10:53=E2=80=AFAM Masahiko Sawada = wrote: >>>>>=20 >>>>> On Mon, Jun 29, 2026 at 5:53=E2=80=AFPM Haibo Yan = wrote: >>>>>>=20 >>>>>> On Mon, Jun 29, 2026 at 2:55=E2=80=AFPM Masahiko Sawada = wrote: >>>>>>>=20 >>>>>>> On Sun, Jun 28, 2026 at 7:20=E2=80=AFPM Haibo Yan = wrote: >>>>>>>>=20 >>>>>>>> On Thu, Jun 25, 2026 at 3:16=E2=80=AFPM Masahiko Sawada = wrote: >>>>>>>>>=20 >>>>>>>>> On Thu, Jun 25, 2026 at 2:31=E2=80=AFPM Haibo Yan = wrote: >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> On Thu, Jun 25, 2026 at 11:28=E2=80=AFAM Masahiko Sawada = wrote: >>>>>>>>>>>=20 >>>>>>>>>>> Hi all, >>>>>>>>>>>=20 >>>>>>>>>>> I'd like to propose the $subject. >>>>>>>>>>>=20 >>>>>>>>>>> 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. >>>>>>>>>>>=20 >>>>>>>>>>> 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. >>>>>>>>>>>=20 >>>>>>>>>>> I've benchmarked the parse speed using the following query: >>>>>>>>>>>=20 >>>>>>>>>>> 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; >>>>>>>>>>>=20 >>>>>>>>>>> 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): >>>>>>>>>>>=20 >>>>>>>>>>> HEAD: 208.879 ms >>>>>>>>>>> Patched: 40.983 ms >>>>>>>>>>>=20 >>>>>>>>>>> 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. >>>>>>>>>>>=20 >>>>>>>>>>> Feedback is very welcome. >>>>>>>>>>>=20 >>>>>>>>>> I may be missing something, but I wonder whether the fast = path is relying on >>>>>>>>>> slightly different input semantics from the existing UUID = parser. >>>>>>>>>>=20 >>>>>>>>>> In particular, hex_decode_safe() is not a strict =E2=80=9C32 = hex characters only=E2=80=9D >>>>>>>>>> decoder. It skips whitespace, which is fine for its existing = callers, but I >>>>>>>>>> don=E2=80=99t think UUID input should treat whitespace inside = the UUID body as >>>>>>>>>> ignorable. >>>>>>>>>=20 >>>>>>>>> Good catch! hex_decode_safe() skips whitespaces so the patch = accepts >>>>>>>>> the following UUID value, which is bad: >>>>>>>>>=20 >>>>>>>>> select '019f00b5-7f8a-722f-b707-59f0ed25cd '::uuid; >>>>>>>>> uuid >>>>>>>>> -------------------------------------- >>>>>>>>> 019f00b5-7f8a-722f-b707-59f0ed25cd00 >>>>>>>>> (1 row) >>>>>>>>>=20 >>>>>>>>>> Also, since hex_decode_safe() returns void, the UUID fast = path >>>>>>>>>> cannot verify that exactly UUID_LEN bytes were produced. >>>>>>>>>=20 >>>>>>>>> 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. >>>>>>>>>=20 >>>>>>>>=20 >>>>>>>> You=E2=80=99re right, I misread that part. Checking both = esctx.error_occurred and >>>>>>>> the returned length sounds good to me. >>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> 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. >>>>>>>>>=20 >>>>>>>>> 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. >>>>>>>>>=20 >>>>>>>>=20 >>>>>>>> That sounds reasonable. My main concern was to keep the fast = path=E2=80=99s 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. >>>>>>>>=20 >>>>>>>>>>=20 >>>>>>>>>> Could you also add a few regression tests for invalid inputs = that contain >>>>>>>>>> whitespace inside otherwise fast-path-looking UUID strings? = For example: >>>>>>>>>>=20 >>>>>>>>>> = --------------------------------------------------------------- >>>>>>>>>>=20 >>>>>>>>>> SELECT 'a0eebc99 9c0b4ef8bb6d6bb9bd380a11'::uuid; >>>>>>>>>> SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a1 '::uuid; >>>>>>>>>> SELECT '{a0eebc999c0b4ef8bb6d6bb9bd380a1 }'::uuid; >>>>>>>>>> SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1 '::uuid; >>>>>>>>>> = --------------------------------------------------------------- >>>>>>>>>>=20 >>>>>>>>>> These should continue to be rejected in the same way as the = scalar parser. >>>>>>>>>> Regards, >>>>>>>>>=20 >>>>>>>>> Agreed. >>>>>>>>>=20 >>>>>>>=20 >>>>>>> I've attached the updated patch. >>>>>>>=20 >>>>>>> Regards, >>>>>>>=20 >>>>>>> -- >>>>>>> Masahiko Sawada >>>>>>> Amazon Web Services: https://aws.amazon.com >>>>>>=20 >>>>>> I noticed a few typos in the comments: >>>>>>=20 >>>>>> src/backend/utils/adt/uuid.c >>>>>> line 56: =E2=80=9Cscalar implmentation=E2=80=9D -> =E2=80=9Cscalar = implementation=E2=80=9D >>>>>> line 109: =E2=80=9Cswalled=E2=80=9D -> =E2=80=9Cswallowed=E2=80=9D >>>>>> line 110: =E2=80=9Ckepping=E2=80=9D -> =E2=80=9Ckeeping=E2=80=9D >>>>>> line 118: =E2=80=9Cgrammer=E2=80=9D -> =E2=80=9Cgrammar=E2=80=9D >>>>>> line 119: =E2=80=9Cwhitespaces=E2=80=9D -> =E2=80=9Cwhitespace=E2=80= =9D >>>>>>=20 >>>>>> Could you fix them ? >>>>>=20 >>>>> Oops, I fixed them and rechecked other places. >>>>>=20 >>>>> I've attached the updated patch. >>>>>=20 >>>>> Regards, >>>>>=20 >>>>> -- >>>>> Masahiko Sawada >>>>> Amazon Web Services: https://aws.amazon.com >>>>=20 >>>> The code looks good to me now. I only noticed one small typo in = the >>>> commit trailer: Reviwed-by should be Reviewed-by. >>>>=20 >>>> Otherwise, it looks good. Thank you for fixing these issues. >>>>=20 >>>=20 >>> After spending more time on this patch, I find out two things: >>>=20 >>> 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. >>>=20 >>> 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: >>>=20 >>> =3D# 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) >>>=20 >>> 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 >>>=20 >>> '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. >>>=20 >>> Regards, >>>=20 >>> -- >>> Masahiko Sawada >>> Amazon Web Services: https://aws.amazon.com >>> = >>=20 >> A few comments on v4. >>=20 >> 1 - 0001 >> ``` >> +static void >> +string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext) >> +{ >> + const char *body =3D source; >> + size_t len =3D strlen(source); >> ``` >>=20 >> 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. >=20 > 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/