Thread

Commits

Same data as JSON: GET /api/v1/messages/:b64id/commits the thread's linked commits as JSON, with link sources. API reference →
  1. Simplify coding style of is_valid_ascii()

  2. Apply auto-vectorization to the inner loop of numeric multiplication.

  1. [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-01T17:32:23Z

    Hi,
    
    As of b80e10638e3, there is a new API for validating the encoding of
    strings, and one of the side effects is that we have a wider choice of
    algorithms. For UTF-8, it has been demonstrated that SIMD is much faster at
    decoding [1] and validation [2] than the standard approach we use.
    
    It makes sense to start with the ascii subset of UTF-8 for a couple
    reasons. First, ascii is very widespread in database content, particularly
    in bulk loads. Second, ascii can be validated using the simple SSE2
    intrinsics that come with (I believe) any x64-64 chip, and I'm guessing we
    can detect that at compile time and not mess with runtime checks. The
    examples above using SSE for the general case are much more complicated and
    involve SSE 4.2 or AVX.
    
    Here are some numbers on my laptop (MacOS/clang 10 -- if the concept is
    okay, I'll do Linux/gcc and add more inputs). The test is the same as
    Heikki shared in [3], but I added a case with >95% Chinese characters just
    to show how that compares to the mixed ascii/multibyte case.
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
        1081 |   761 |   366
    
    patch:
    
     chinese | mixed | ascii
    ---------+-------+-------
        1103 |   498 |    51
    
    The speedup in the pure ascii case is nice.
    
    In the attached POC, I just have a pro forma portability stub, and left
    full portability detection for later. The fast path is inlined inside
    pg_utf8_verifystr(). I imagine the ascii fast path could be abstracted into
    a separate function to which is passed a function pointer for full encoding
    validation. That would allow other encodings with strict ascii subsets to
    use this as well, but coding that abstraction might be a little messy, and
    b80e10638e3 already gives a performance boost over PG13.
    
    I also gave a shot at doing full UTF-8 recognition using a DFA, but so far
    that has made performance worse. If I ever have more success with that,
    I'll add that in the mix.
    
    [1] https://woboq.com/blog/utf-8-processing-using-simd.html
    [2]
    https://lemire.me/blog/2020/10/20/ridiculously-fast-unicode-utf-8-validation/
    [3]
    https://www.postgresql.org/message-id/06d45421-61b8-86dd-e765-f1ce527a5a2f@iki.fi
    
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com
    
  2. Re: [POC] verifying UTF-8 using SIMD instructions

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-02-01T18:01:50Z

    On 01/02/2021 19:32, John Naylor wrote:
    > It makes sense to start with the ascii subset of UTF-8 for a couple 
    > reasons. First, ascii is very widespread in database content, 
    > particularly in bulk loads. Second, ascii can be validated using the 
    > simple SSE2 intrinsics that come with (I believe) any x64-64 chip, and 
    > I'm guessing we can detect that at compile time and not mess with 
    > runtime checks. The examples above using SSE for the general case are 
    > much more complicated and involve SSE 4.2 or AVX.
    
    I wonder how using SSE compares with dealing with 64 or 32-bit words at 
    a time, using regular instructions? That would be more portable.
    
    > Here are some numbers on my laptop (MacOS/clang 10 -- if the concept is 
    > okay, I'll do Linux/gcc and add more inputs). The test is the same as 
    > Heikki shared in [3], but I added a case with >95% Chinese characters 
    > just to show how that compares to the mixed ascii/multibyte case.
    > 
    > master:
    > 
    >   chinese | mixed | ascii
    > ---------+-------+-------
    >      1081 |   761 |   366
    > 
    > patch:
    > 
    >   chinese | mixed | ascii
    > ---------+-------+-------
    >      1103 |   498 |    51
    > 
    > The speedup in the pure ascii case is nice.
    
    Yep.
    
    > In the attached POC, I just have a pro forma portability stub, and left 
    > full portability detection for later. The fast path is inlined inside 
    > pg_utf8_verifystr(). I imagine the ascii fast path could be abstracted 
    > into a separate function to which is passed a function pointer for full 
    > encoding validation. That would allow other encodings with strict ascii 
    > subsets to use this as well, but coding that abstraction might be a 
    > little messy, and b80e10638e3 already gives a performance boost over PG13.
    
    All supported encodings are ASCII subsets. Might be best to putt the 
    ASCII-check into a static inline function and use it in all the verify 
    functions. I presume it's only a few instructions, and these functions 
    can be pretty performance sensitive.
    
    > I also gave a shot at doing full UTF-8 recognition using a DFA, but so 
    > far that has made performance worse. If I ever have more success with 
    > that, I'll add that in the mix.
    
    That's disappointing. Perhaps the SIMD algorithms have higher startup 
    costs, so that you need longer inputs to benefit? In that case, it might 
    make sense to check the length of the input and only use the SIMD 
    algorithm if the input is long enough.
    
    - Heikki
    
    
    
    
  3. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-04T21:48:35Z

    On Mon, Feb 1, 2021 at 2:01 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > On 01/02/2021 19:32, John Naylor wrote:
    > > It makes sense to start with the ascii subset of UTF-8 for a couple
    > > reasons. First, ascii is very widespread in database content,
    > > particularly in bulk loads. Second, ascii can be validated using the
    > > simple SSE2 intrinsics that come with (I believe) any x64-64 chip, and
    > > I'm guessing we can detect that at compile time and not mess with
    > > runtime checks. The examples above using SSE for the general case are
    > > much more complicated and involve SSE 4.2 or AVX.
    >
    > I wonder how using SSE compares with dealing with 64 or 32-bit words at
    > a time, using regular instructions? That would be more portable.
    
    I gave that a shot, and it's actually pretty good. According to this paper,
    [1], 16 bytes was best and gives a good apples-to-apples comparison to SSE
    registers, so I tried both 16 and 8 bytes.
    
    > All supported encodings are ASCII subsets. Might be best to putt the
    > ASCII-check into a static inline function and use it in all the verify
    > functions. I presume it's only a few instructions, and these functions
    > can be pretty performance sensitive.
    
    I tried both the static inline function and also putting the whole
    optimized utf-8 loop in a separate function to which the caller passes a
    pointer to the appropriate pg_*_verifychar().
    
    In the table below, "inline" refers to coding directly inside
    pg_utf8_verifystr(). Both C and SSE are in the same patch, with an #ifdef.
    I didn't bother splitting them out because for other encodings, we want one
    of the other approaches above. For those, "C retail" refers to a static
    inline function to code the contents of the inner loop, if I understood
    your suggestion correctly. This needs more boilerplate in each function, so
    I don't prefer this. "C func pointer" refers to the pointer approach I just
    mentioned. That is the cleanest looking way to generalize it, so I only
    tested that version with different strides -- 8- and 16-bytes
    
    This is the same test I used earlier, which is the test in [2] but adding
    an almost-pure multibyte Chinese text of about the same size.
    
    x64-64 Linux gcc 8.4.0:
    
          build       | chinese | mixed | ascii
    ------------------+---------+-------+-------
     master           |    1480 |   848 |   428
     inline SSE       |    1617 |   634 |    63
     inline C         |    1481 |   843 |    50
     C retail         |    1493 |   838 |    49
     C func pointer   |    1467 |   851 |    49
     C func pointer 8 |    1518 |   757 |    56
    
    x64-64 MacOS clang 10.0.0:
    
          build       | chinese | mixed | ascii
    ------------------+---------+-------+-------
     master           |    1086 |   760 |   374
     inline SSE       |    1081 |   529 |    70
     inline C         |    1093 |   649 |    49
     C retail         |    1132 |   695 |   152
     C func pointer   |    1085 |   609 |    59
     C func pointer 8 |    1099 |   571 |    71
    
    PowerPC-LE Linux gcc 4.8.5:
    
          build       | chinese | mixed | ascii
    ------------------+---------+-------+-------
     master           |    2961 |  1525 |   871
     inline SSE       |   (n/a) | (n/a) | (n/a)
     inline C         |    2911 |  1329 |    80
     C retail         |    2838 |  1311 |   102
     C func pointer   |    2828 |  1314 |    80
     C func pointer 8 |    3143 |  1249 |   133
    
    Looking at the results, the main advantage of SSE here is it's more robust
    for mixed inputs. If a 16-byte chunk is not ascii-only but contains a block
    of ascii at the front, we can skip those with a single CPU instruction, but
    in C, we have to verify the whole chunk using the slow path.
    
    The "C func pointer approach" seems to win out over the "C retail" approach
    (static inline function).
    
    Using an 8-byte stride is slightly better for mixed inputs on all platforms
    tested, but regresses on pure ascii and also seems to regress on pure
    multibyte. The difference in the multibyte caes is small enough that it
    could be random, but it happens on two platforms, so I'd say it's real. On
    the other hand, pure multibyte is not as common as mixed text.
    
    Overall, I think the function pointer approach with an 8-byte stride is the
    best balance. If that's agreeable, next I plan to test with short inputs,
    because I think we'll want a guard if-statement to only loop through the
    fast path if the string is long enough to justify that.
    
    > > I also gave a shot at doing full UTF-8 recognition using a DFA, but so
    > > far that has made performance worse. If I ever have more success with
    > > that, I'll add that in the mix.
    >
    > That's disappointing. Perhaps the SIMD algorithms have higher startup
    > costs, so that you need longer inputs to benefit? In that case, it might
    > make sense to check the length of the input and only use the SIMD
    > algorithm if the input is long enough.
    
    I changed topics a bit quickly, but here I'm talking about using a
    table-driven state machine to verify the multibyte case. It's possible I
    did something wrong, since my model implementation decodes, and having to
    keep track of how many bytes got verified might be the culprit. I'd like to
    try again to speed up multibyte, but that might be a PG15 project.
    
    [1] https://arxiv.org/abs/2010.03090
    [2]
    https://www.postgresql.org/message-id/06d45421-61b8-86dd-e765-f1ce527a5a2f@iki.fi
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  4. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-07T20:24:16Z

    Here is a more polished version of the function pointer approach, now
    adapted to all multibyte encodings. Using the not-yet-committed tests from
    [1], I found a thinko bug that resulted in the test for nul bytes to not
    only be wrong, but probably also elided by the compiler. Doing it correctly
    is noticeably slower on pure ascii, but still several times faster than
    before, so the conclusions haven't changed any. I'll run full measurements
    later this week, but I'll share the patch now for review.
    
    [1]
    https://www.postgresql.org/message-id/11d39e63-b80a-5f8d-8043-fff04201fadc@iki.fi
    
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com
    
  5. Re: [POC] verifying UTF-8 using SIMD instructions

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-02-08T10:17:11Z

    On 07/02/2021 22:24, John Naylor wrote:
    > Here is a more polished version of the function pointer approach, now 
    > adapted to all multibyte encodings. Using the not-yet-committed tests 
    > from [1], I found a thinko bug that resulted in the test for nul bytes 
    > to not only be wrong, but probably also elided by the compiler. Doing it 
    > correctly is noticeably slower on pure ascii, but still several times 
    > faster than before, so the conclusions haven't changed any. I'll run 
    > full measurements later this week, but I'll share the patch now for review.
    
    As a quick test, I hacked up pg_utf8_verifystr() to use Lemire's 
    algorithm from the simdjson library [1], see attached patch. I 
    microbenchmarked it using the the same test I used before [2].
    
    These results are with "gcc -O2" using "gcc (Debian 10.2.1-6) 10.2.1 
    20210110"
    
    unpatched master:
    
    postgres=# \i mbverifystr-speed.sql
    CREATE FUNCTION
      mixed | ascii
    -------+-------
        728 |   393
    (1 row)
    
    v1-0001-Add-an-ASCII-fast-path-to-multibyte-encoding-veri.patch:
    
      mixed | ascii
    -------+-------
        759 |    98
    (1 row)
    
    simdjson-utf8-hack.patch:
    
      mixed | ascii
    -------+-------
         53 |    31
    (1 row)
    
    So clearly that algorithm is fast. Not sure if it has a high startup 
    cost, or large code size, or other tradeoffs that we don't want. At 
    least it depends on SIMD instructions, so it requires more code for the 
    architecture-specific implementations and autoconf logic and all that. 
    Nevertheless I think it deserves a closer look, I'm a bit reluctant to 
    put in half-way measures, when there's a clearly superior algorithm out 
    there.
    
    I also tested the fallback implementation from the simdjson library 
    (included in the patch, if you uncomment it in simdjson-glue.c):
    
      mixed | ascii
    -------+-------
        447 |    46
    (1 row)
    
    I think we should at least try to adopt that. At a high level, it looks 
    pretty similar your patch: you load the data 8 bytes at a time, check if 
    there are all ASCII. If there are any non-ASCII chars, you check the 
    bytes one by one, otherwise you load the next 8 bytes. Your patch should 
    be able to achieve the same performance, if done right. I don't think 
    the simdjson code forbids \0 bytes, so that will add a few cycles, but 
    still.
    
    [1] https://github.com/simdjson/simdjson
    [2] 
    https://www.postgresql.org/message-id/06d45421-61b8-86dd-e765-f1ce527a5a2f@iki.fi
    
    - Heikki
    
    PS. Your patch as it stands isn't safe on systems with strict alignment, 
    the string passed to the verify function isn't guaranteed to be 8 bytes 
    aligned. Use memcpy to fetch the next 8-byte chunk to fix.
    
    
  6. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-08T13:14:44Z

    On Mon, Feb 8, 2021 at 6:17 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    > As a quick test, I hacked up pg_utf8_verifystr() to use Lemire's
    > algorithm from the simdjson library [1], see attached patch. I
    > microbenchmarked it using the the same test I used before [2].
    
    I've been looking at various iterations of Lemire's utf8 code, and trying
    it out was next on my list, so thanks for doing that!
    
    > These results are with "gcc -O2" using "gcc (Debian 10.2.1-6) 10.2.1
    > 20210110"
    >
    > unpatched master:
    >
    > postgres=# \i mbverifystr-speed.sql
    > CREATE FUNCTION
    >   mixed | ascii
    > -------+-------
    >     728 |   393
    > (1 row)
    >
    > v1-0001-Add-an-ASCII-fast-path-to-multibyte-encoding-veri.patch:
    >
    >   mixed | ascii
    > -------+-------
    >     759 |    98
    > (1 row)
    
    Hmm, the mixed case got worse -- I haven't seen that in any of my tests.
    
    > simdjson-utf8-hack.patch:
    >
    >   mixed | ascii
    > -------+-------
    >      53 |    31
    > (1 row)
    >
    > So clearly that algorithm is fast. Not sure if it has a high startup
    > cost, or large code size, or other tradeoffs that we don't want.
    
    The simdjson lib uses everything up through AVX512 depending on what
    hardware is available. I seem to remember reading that high start-up cost
    is more relevant to floating point than to integer ops, but I could be
    wrong. Just the utf8 portion is surely tiny also.
    
    > At
    > least it depends on SIMD instructions, so it requires more code for the
    > architecture-specific implementations and autoconf logic and all that.
    
    One of his earlier demos [1] (in simdutf8check.h) had a version that used
    mostly SSE2 with just three intrinsics from SSSE3. That's widely available
    by now. He measured that at 0.7 cycles per byte, which is still good
    compared to AVX2 0.45 cycles per byte [2].
    
    Testing for three SSSE3 intrinsics in autoconf is pretty easy. I would
    assume that if that check (and the corresponding runtime check) passes, we
    can assume SSE2. That code has three licenses to choose from -- Apache 2,
    Boost, and MIT. Something like that might be straightforward to start
    from. I think the only obstacles to worry about are license and getting it
    to fit into our codebase. Adding more than zero high-level comments with a
    good description of how it works in detail is also a bit of a challenge.
    
    > I also tested the fallback implementation from the simdjson library
    > (included in the patch, if you uncomment it in simdjson-glue.c):
    >
    >   mixed | ascii
    > -------+-------
    >     447 |    46
    > (1 row)
    >
    > I think we should at least try to adopt that. At a high level, it looks
    > pretty similar your patch: you load the data 8 bytes at a time, check if
    > there are all ASCII. If there are any non-ASCII chars, you check the
    > bytes one by one, otherwise you load the next 8 bytes. Your patch should
    > be able to achieve the same performance, if done right. I don't think
    > the simdjson code forbids \0 bytes, so that will add a few cycles, but
    > still.
    
    Okay, I'll look into that.
    
    > PS. Your patch as it stands isn't safe on systems with strict alignment,
    > the string passed to the verify function isn't guaranteed to be 8 bytes
    > aligned. Use memcpy to fetch the next 8-byte chunk to fix.
    
    Will do.
    
    [1] https://github.com/lemire/fastvalidate-utf-8/tree/master/include
    [2]
    https://lemire.me/blog/2018/10/19/validating-utf-8-bytes-using-only-0-45-cycles-per-byte-avx-edition/
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  7. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-09T20:08:21Z

    On Mon, Feb 8, 2021 at 6:17 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > I also tested the fallback implementation from the simdjson library
    > (included in the patch, if you uncomment it in simdjson-glue.c):
    >
    >   mixed | ascii
    > -------+-------
    >     447 |    46
    > (1 row)
    >
    > I think we should at least try to adopt that. At a high level, it looks
    > pretty similar your patch: you load the data 8 bytes at a time, check if
    > there are all ASCII. If there are any non-ASCII chars, you check the
    > bytes one by one, otherwise you load the next 8 bytes. Your patch should
    > be able to achieve the same performance, if done right. I don't think
    > the simdjson code forbids \0 bytes, so that will add a few cycles, but
    > still.
    
    That fallback is very similar to my "inline C" case upthread, and they both
    actually check 16 bytes at a time (the comment is wrong in the patch you
    shared). I can work back and show how the performance changes with each
    difference (just MacOS, clang 10 here):
    
    master
    
     mixed | ascii
    -------+-------
       757 |   366
    
    v1, but using memcpy()
    
     mixed | ascii
    -------+-------
       601 |   129
    
    remove zero-byte check:
    
     mixed | ascii
    -------+-------
       588 |    93
    
    inline ascii fastpath into pg_utf8_verifystr()
    
     mixed | ascii
    -------+-------
       595 |    71
    
    use 16-byte stride
    
     mixed | ascii
    -------+-------
       652 |    49
    
    With this cpu/compiler, v1 is fastest on the mixed input all else being
    equal.
    
    Maybe there's a smarter way to check for zeros in C. Or maybe be more
    careful about cache -- running memchr() on the whole input first might not
    be the best thing to do.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  8. Re: [POC] verifying UTF-8 using SIMD instructions

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-02-09T20:22:02Z

    On 09/02/2021 22:08, John Naylor wrote:
    > Maybe there's a smarter way to check for zeros in C. Or maybe be more 
    > careful about cache -- running memchr() on the whole input first might 
    > not be the best thing to do.
    
    The usual trick is the haszero() macro here: 
    https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord. That's 
    how memchr() is typically implemented, too.
    
    - Heikki
    
    
    
    
  9. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-09T21:12:22Z

    I wrote:
    >
    > On Mon, Feb 8, 2021 at 6:17 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    > One of his earlier demos [1] (in simdutf8check.h) had a version that used
    mostly SSE2 with just three intrinsics from SSSE3. That's widely available
    by now. He measured that at 0.7 cycles per byte, which is still good
    compared to AVX2 0.45 cycles per byte [2].
    >
    > Testing for three SSSE3 intrinsics in autoconf is pretty easy. I would
    assume that if that check (and the corresponding runtime check) passes, we
    can assume SSE2. That code has three licenses to choose from -- Apache 2,
    Boost, and MIT. Something like that might be straightforward to start from.
    I think the only obstacles to worry about are license and getting it to fit
    into our codebase. Adding more than zero high-level comments with a good
    description of how it works in detail is also a bit of a challenge.
    
    I double checked, and it's actually two SSSE3 intrinsics and one SSE4.1,
    but the 4.1 one can be emulated with a few SSE2 intrinsics. But we could
    probably fold all three into the SSE4.2 CRC check and have a single symbol
    to save on boilerplate.
    
    I hacked that demo [1] into wchar.c (very ugly patch attached), and got the
    following:
    
    master
    
     mixed | ascii
    -------+-------
       757 |   366
    
    Lemire demo:
    
     mixed | ascii
    -------+-------
       172 |   168
    
    This one lacks an ascii fast path, but the AVX2 version in the same file
    has one that could probably be easily adapted. With that, I think this
    would be worth adapting to our codebase and license. Thoughts?
    
    The advantage of this demo is that it's not buried in a mountain of modern
    C++.
    
    Simdjson can use AVX -- do you happen to know which target it got compiled
    to? AVX vectors are 256-bits wide and that requires OS support. The OS's we
    care most about were updated 8-12 years ago, but that would still be
    something to check, in addition to more configure checks.
    
    [1] https://github.com/lemire/fastvalidate-utf-8/tree/master/include
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  10. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-10T04:00:53Z

    On Tue, Feb 9, 2021 at 4:22 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > On 09/02/2021 22:08, John Naylor wrote:
    > > Maybe there's a smarter way to check for zeros in C. Or maybe be more
    > > careful about cache -- running memchr() on the whole input first might
    > > not be the best thing to do.
    >
    > The usual trick is the haszero() macro here:
    > https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord. That's
    > how memchr() is typically implemented, too.
    
    Thanks for that. Checking with that macro each loop iteration gives a small
    boost:
    
    v1, but using memcpy()
    
     mixed | ascii
    -------+-------
       601 |   129
    
    with haszero()
    
     mixed | ascii
    -------+-------
       583 |   105
    
    remove zero-byte check:
    
     mixed | ascii
    -------+-------
       588 |    93
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  11. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-13T01:31:33Z

    On Mon, Feb 8, 2021 at 6:17 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > I also tested the fallback implementation from the simdjson library
    > (included in the patch, if you uncomment it in simdjson-glue.c):
    >
    >   mixed | ascii
    > -------+-------
    >     447 |    46
    > (1 row)
    >
    > I think we should at least try to adopt that. At a high level, it looks
    > pretty similar your patch: you load the data 8 bytes at a time, check if
    > there are all ASCII. If there are any non-ASCII chars, you check the
    > bytes one by one, otherwise you load the next 8 bytes. Your patch should
    > be able to achieve the same performance, if done right. I don't think
    > the simdjson code forbids \0 bytes, so that will add a few cycles, but
    > still.
    
    Attached is a patch that does roughly what simdjson fallback did, except I
    use straight tests on the bytes and only calculate code points in assertion
    builds. In the course of doing this, I found that my earlier concerns about
    putting the ascii check in a static inline function were due to my
    suboptimal loop implementation. I had assumed that if the chunked ascii
    check failed, it had to check all those bytes one at a time. As it turns
    out, that's a waste of the branch predictor. In the v2 patch, we do the
    chunked ascii check every time we loop. With that, I can also confirm the
    claim in the Lemire paper that it's better to do the check on 16-byte
    chunks:
    
    (MacOS, Clang 10)
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
        1081 |   761 |   366
    
    v2 patch, with 16-byte stride:
    
     chinese | mixed | ascii
    ---------+-------+-------
         806 |   474 |    83
    
    patch but with 8-byte stride:
    
     chinese | mixed | ascii
    ---------+-------+-------
         792 |   490 |   105
    
    I also included the fast path in all other multibyte encodings, and that is
    also pretty good performance-wise. It regresses from master on pure
    multibyte input, but that case is still faster than PG13, which I simulated
    by reverting 6c5576075b0f9 and b80e10638e3:
    
    ~PG13:
    
     chinese | mixed | ascii
    ---------+-------+-------
        1565 |   848 |   365
    
    ascii fast-path plus pg_*_verifychar():
    
     chinese | mixed | ascii
    ---------+-------+-------
        1279 |   656 |    94
    
    
    v2 has a rough start to having multiple implementations in
    src/backend/port. Next steps are:
    
    1. Add more tests for utf-8 coverage (in addition to the ones to be added
    by the noError argument patch)
    2. Add SSE4 validator -- it turns out the demo I referred to earlier
    doesn't match the algorithm in the paper. I plan to only copy the lookup
    tables from simdjson verbatim, but the code will basically be written from
    scratch, using  simdjson as a hint.
    3. Adjust configure.ac
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  12. Re: [POC] verifying UTF-8 using SIMD instructions

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-02-15T13:18:09Z

    On 13/02/2021 03:31, John Naylor wrote:
    > On Mon, Feb 8, 2021 at 6:17 AM Heikki Linnakangas <hlinnaka@iki.fi 
    > <mailto:hlinnaka@iki.fi>> wrote:
    >  >
    >  > I also tested the fallback implementation from the simdjson library
    >  > (included in the patch, if you uncomment it in simdjson-glue.c):
    >  >
    >  >   mixed | ascii
    >  > -------+-------
    >  >     447 |    46
    >  > (1 row)
    >  >
    >  > I think we should at least try to adopt that. At a high level, it looks
    >  > pretty similar your patch: you load the data 8 bytes at a time, check if
    >  > there are all ASCII. If there are any non-ASCII chars, you check the
    >  > bytes one by one, otherwise you load the next 8 bytes. Your patch should
    >  > be able to achieve the same performance, if done right. I don't think
    >  > the simdjson code forbids \0 bytes, so that will add a few cycles, but
    >  > still.
    > 
    > Attached is a patch that does roughly what simdjson fallback did, except 
    > I use straight tests on the bytes and only calculate code points in 
    > assertion builds. In the course of doing this, I found that my earlier 
    > concerns about putting the ascii check in a static inline function were 
    > due to my suboptimal loop implementation. I had assumed that if the 
    > chunked ascii check failed, it had to check all those bytes one at a 
    > time. As it turns out, that's a waste of the branch predictor. In the v2 
    > patch, we do the chunked ascii check every time we loop. With that, I 
    > can also confirm the claim in the Lemire paper that it's better to do 
    > the check on 16-byte chunks:
    > 
    > (MacOS, Clang 10)
    > 
    > master:
    > 
    >   chinese | mixed | ascii
    > ---------+-------+-------
    >      1081 |   761 |   366
    > 
    > v2 patch, with 16-byte stride:
    > 
    >   chinese | mixed | ascii
    > ---------+-------+-------
    >       806 |   474 |    83
    > 
    > patch but with 8-byte stride:
    > 
    >   chinese | mixed | ascii
    > ---------+-------+-------
    >       792 |   490 |   105
    > 
    > I also included the fast path in all other multibyte encodings, and that 
    > is also pretty good performance-wise.
    
    Cool.
    
    > It regresses from master on pure 
    > multibyte input, but that case is still faster than PG13, which I 
    > simulated by reverting 6c5576075b0f9 and b80e10638e3:
    
    I thought the "chinese" numbers above are pure multibyte input, and it 
    seems to do well on that. Where does it regress? In multibyte encodings 
    other than UTF-8? How bad is the regression?
    
    I tested this on my first generation Raspberry Pi (chipmunk). I had to 
    tweak it a bit to make it compile, since the SSE autodetection code was 
    not finished yet. And I used generate_series(1, 1000) instead of 
    generate_series(1, 10000) in the test script (mbverifystr-speed.sql) 
    because this system is so slow.
    
    master:
    
      mixed | ascii
    -------+-------
       1310 |  1041
    (1 row)
    
    v2-add-portability-stub-and-new-fallback.patch:
    
      mixed | ascii
    -------+-------
       2979 |   910
    (1 row)
    
    I'm guessing that's because the unaligned access in check_ascii() is 
    expensive on this platform.
    
    - Heikki
    
    
    
    
  13. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-16T01:32:52Z

    On Mon, Feb 15, 2021 at 9:18 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    
    Attached is the first attempt at using SSE4 to do the validation, but first
    I'll answer your questions about the fallback.
    
    I should mention that v2 had a correctness bug for 4-byte characters that I
    found when I was writing regression tests. It shouldn't materially affect
    performance, however.
    
    > I thought the "chinese" numbers above are pure multibyte input, and it
    > seems to do well on that. Where does it regress? In multibyte encodings
    > other than UTF-8?
    
    Yes, the second set of measurements was intended to represent multibyte
    encodings other than UTF-8. But instead of using one of those encodings, I
    simulated non-UTF-8 by copying the pattern used for those: in the loop,
    check for ascii then either advance or verify one character. It was a quick
    way to use the same test.
    
    > How bad is the regression?
    
    I'll copy the measurements here together with master so it's easier to
    compare:
    
    ~= PG13 (revert 6c5576075b0f9 and b80e10638e3):
    
     chinese | mixed | ascii
    ---------+-------+-------
        1565 |   848 |   365
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
        1081 |   761 |   366
    
    ascii fast-path plus pg_*_verifychar():
    
     chinese | mixed | ascii
    ---------+-------+-------
        1279 |   656 |    94
    
    As I mentioned upthread, pure multibyte is still faster than PG13. Reducing
    the ascii check to 8-bytes at time might alleviate the regression.
    
    > I tested this on my first generation Raspberry Pi (chipmunk). I had to
    > tweak it a bit to make it compile, since the SSE autodetection code was
    > not finished yet. And I used generate_series(1, 1000) instead of
    > generate_series(1, 10000) in the test script (mbverifystr-speed.sql)
    > because this system is so slow.
    >
    > master:
    >
    >   mixed | ascii
    > -------+-------
    >    1310 |  1041
    > (1 row)
    >
    > v2-add-portability-stub-and-new-fallback.patch:
    >
    >   mixed | ascii
    > -------+-------
    >    2979 |   910
    > (1 row)
    >
    > I'm guessing that's because the unaligned access in check_ascii() is
    > expensive on this platform.
    
    Hmm, I used memcpy() as suggested. Is that still slow on that platform?
    That's 32-bit, right? Some possible remedies:
    
    1) For the COPY FROM case, we should align the allocation on a cacheline --
    we already have examples of that idiom elsewhere. I was actually going to
    suggest doing this anyway, since unaligned SIMD loads are often slower, too.
    
    2) As the simdjson fallback was based on Fuchsia (the Lemire paper implies
    it was tested carefully on Arm and I have no reason to doubt that), I could
    try to follow that example more faithfully by computing the actual
    codepoints. It's more computation and just as many branches as far as I can
    tell, but it's not a lot of work. I can add that alternative fallback to
    the patch set. I have no Arm machines, but I can test on a POWER8 machine.
    
    3) #ifdef out the ascii check for 32-bit platforms.
    
    4) Same as the non-UTF8 case -- only check for ascii 8 bytes at a time.
    I'll probably try this first.
    
    Now, I'm pleased to report that I got SSE4 working, and it seems to work.
    It still needs some stress testing to find any corner case bugs, but it
    shouldn't be too early to share some numbers on Clang 10 / MacOS:
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
        1082 |   751 |   364
    
    v3 with SSE4.1:
    
     chinese | mixed | ascii
    ---------+-------+-------
         127 |   128 |   126
    
    Some caveats and notes:
    
    - It takes almost no recognizable code from simdjson, but it does take the
    magic constants lookup tables almost verbatim. The main body of the code
    has no intrinsics at all (I think). They're all hidden inside static inline
    helper functions. I reused some cryptic variable names from simdjson. It's
    a bit messy but not terrible.
    
    - It diffs against the noError conversion patch and adds additional tests.
    
    - It's not smart enough to stop at the last valid character boundary --
    it's either all-valid or it must start over with the fallback. That will
    have to change in order to work with the proposed noError conversions. It
    shouldn't be very hard, but needs thought as to the clearest and safest way
    to code it.
    
    - There is no ascii fast-path yet. With this algorithm we have to be a bit
    more careful since a valid ascii chunk could be preceded by an incomplete
    sequence at the end of the previous chunk. Not too hard, just a bit more
    work.
    
    - This is my first time hacking autoconf, and it still seems slightly
    broken, yet functional on my machine at least.
    
    - It only needs SSE4.1, but I didn't want to create a whole new CFLAGS, so
    it just reuses SSE4.2 for the runtime check and the macro names. Also, it
    doesn't test for SSE2, it just insists on 64-bit for the runtime check. I
    imagine it would refuse to build on 32-bit machines if you passed it -msse42
    
    - There is a placeholder for Windows support, but it's not developed.
    
    - I had to add a large number of casts to get rid of warnings in the magic
    constants macros. That needs some polish.
    
    I also attached a C file that visually demonstrates every step of the
    algorithm following the example found in Table 9 in the paper. That
    contains the skeleton coding I started with and got abandoned early, so it
    might differ from the actual patch.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  14. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-17T05:40:32Z

    I wrote:
    
    > [v3]
    > - It's not smart enough to stop at the last valid character boundary --
    it's either all-valid or it must start over with the fallback. That will
    have to change in order to work with the proposed noError conversions. It
    shouldn't be very hard, but needs thought as to the clearest and safest way
    to code it.
    
    In v4, it should be able to return an accurate count of valid bytes even
    when the end crosses a character boundary.
    
    > - This is my first time hacking autoconf, and it still seems slightly
    broken, yet functional on my machine at least.
    
    It was actually completely broken if you tried to pass the special flags to
    configure. I redesigned this part and it seems to work now.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  15. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-19T00:43:04Z

    On Mon, Feb 15, 2021 at 9:32 PM John Naylor <john.naylor@enterprisedb.com>
    wrote:
    >
    > On Mon, Feb 15, 2021 at 9:18 AM Heikki Linnakangas <hlinnaka@iki.fi>
    wrote:
    > >
    > > I'm guessing that's because the unaligned access in check_ascii() is
    > > expensive on this platform.
    
    > Some possible remedies:
    
    > 3) #ifdef out the ascii check for 32-bit platforms.
    
    > 4) Same as the non-UTF8 case -- only check for ascii 8 bytes at a time.
    I'll probably try this first.
    
    I've attached a couple patches to try on top of v4; maybe they'll help the
    Arm32 regression. 01 reduces the stride to 8 bytes, and 02 applies on top
    of v1 to disable the fallback fast path entirely on 32-bit platforms. A bit
    of a heavy hammer, but it'll confirm (or not) your theory about unaligned
    loads.
    
    Also, I've included patches to explain more fully how I modeled non-UTF-8
    performance while still using the UTF-8 tests. I think it was a useful
    thing to do, and I have a theory that might predict how a non-UTF8 encoding
    will perform with the fast path.
    
    03A and 03B are independent of each other and conflict, but both apply on
    top of v4 (don't need 02). Both replace the v4 fallback with the ascii
    fastpath + pg_utf8_verifychar() in the loop, similar to utf-8 on master.
    03A has a local static copy of pg_utf8_islegal(), and 03B uses the existing
    global function. (On x86, you can disable SSE4 by passing
    USE_FALLBACK_UTF8=1 to configure.)
    
    While Clang 10 regressed for me on pure multibyte in a similar test
    upthread, on Linux gcc 8.4 there isn't a regression at all. IIRC, gcc
    wasn't as good as Clang when the API changed a few weeks ago, so its
    regression from v4 is still faster than master. Clang only regressed with
    my changes because it somehow handled master much better to begin with.
    
    x86-64 Linux gcc 8.4
    
    master
    
     chinese | mixed | ascii
    ---------+-------+-------
        1453 |   857 |   428
    
    v4 (fallback verifier written as a single function)
    
     chinese | mixed | ascii
    ---------+-------+-------
         815 |   514 |    82
    
    v4 plus addendum 03A -- emulate non-utf-8 using a copy of
    pg_utf8_is_legal() as a static function
    
     chinese | mixed | ascii
    ---------+-------+-------
        1115 |   547 |    87
    
    v4 plus addendum 03B -- emulate non-utf-8 using pg_utf8_is_legal() as a
    global function
    
     chinese | mixed | ascii
    ---------+-------+-------
        1279 |   604 |    82
    
    (I also tried the same on ppc64le Linux, gcc 4.8.5 and while not great, it
    never got worse than master either on pure multibyte.)
    
    This is supposed to model the performance of a non-utf8 encoding, where we
    don't have a bespoke function written from scratch. Here's my theory: If an
    encoding has pg_*_mblen(), a global function, inside pg_*_verifychar(), it
    seems it won't benefit as much from an ascii fast path as one whose
    pg_*_verifychar() has no function calls. I'm not sure whether a compiler
    can inline a global function's body into call sites in the unit where it's
    defined. (I haven't looked at the assembly.) But recall that you didn't
    commit 0002 from the earlier encoding change, because it wasn't performing.
    I looked at that patch again, and while it inlined the pg_utf8_verifychar()
    call, it still called the global function pg_utf8_islegal().
    
    If the above is anything to go by, on gcc at least, I don't think we need
    to worry about a regression when adding an ascii fast path to non-utf-8
    multibyte encodings.
    
    Regarding SSE, I've added an ascii fast path in my local branch, but it's
    not going to be as big a difference because 1) the check is more expensive
    in terms of branches than the C case, and 2) because the general case is so
    fast already, it's hard to improve upon. I just need to do some testing and
    cleanup on the whole thing, and that'll be ready to share.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  16. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-20T21:10:58Z

    I made some substantial improvements in v5, and I've taken care of all my
    TODOs below. I separated out the non-UTF-8 ascii fast path into a separate
    patch, since it's kind of off-topic, and it's not yet clear it's always the
    best thing to do.
    
    > - It takes almost no recognizable code from simdjson, but it does take
    the magic constants lookup tables almost verbatim. The main body of the
    code has no intrinsics at all (I think). They're all hidden inside static
    inline helper functions. I reused some cryptic variable names from
    simdjson. It's a bit messy but not terrible.
    
    In v5, the lookup tables and their comments are cleaned up and modified to
    play nice with pgindent.
    
    > - It diffs against the noError conversion patch and adds additional tests.
    
    I wanted to get some cfbot testing, so I went ahead and prepended v4 of
    Heikki's noError patch so it would apply against master.
    
    > - There is no ascii fast-path yet. With this algorithm we have to be a
    bit more careful since a valid ascii chunk could be preceded by an
    incomplete sequence at the end of the previous chunk. Not too hard, just a
    bit more work.
    
    v5 adds an ascii fast path.
    
    > - I had to add a large number of casts to get rid of warnings in the
    magic constants macros. That needs some polish.
    
    This is much nicer now, only one cast really necessary.
    
    I'm pretty pleased with how it is now, but it could use some thorough
    testing for correctness. I'll work on that a bit later.
    
    On my laptop, Clang 10:
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
        1081 |   761 |   366
    
    v5:
    
     chinese | mixed | ascii
    ---------+-------+-------
         136 |    93 |    54
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  17. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-24T16:25:49Z

    The cfbot reported a build failure on Windows because of the use of binary
    literals. I've turned those into hex for v6, so let's see how far it gets
    now.
    
    I also decided to leave out the patch that adds an ascii fast path to
    non-UTF-8 encodings. That would really require more testing than I have
    time for.
    
    As before, 0001 is v4 of Heikk's noError conversion patch, whose
    regressions tests I build upon.
    
    0002 has no ascii fast path in the fallback implementation. 0003 and 0004
    add it back in using 8- and 16-byte strides, respectively. That will make
    it easier to test on non-Intel platforms, so we can decide which way to go
    here. Also did a round of editing the comments in the SSE4.2 file.
    
    I ran the multibyte conversion regression test found in the message below,
    and it passed. That doesn't test UTF-8 explicitly, but all conversions
    round-trip through UTF-8, so it does get some coverage.
    
    https://www.postgresql.org/message-id/b9e3167f-f84b-7aa4-5738-be578a4db924%40iki.fi
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com
    
  18. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-02-24T21:50:50Z

    v7 fixes an obvious mistake in Solution.pm
    
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com
    
  19. Re: [POC] verifying UTF-8 using SIMD instructions

    Amit Khandekar <amitdkhan.pg@gmail.com> — 2021-03-09T09:00:21Z

    Hi,
    
    Just a quick question before I move on to review the patch ... The
    improvement looks like it is only meant for x86 platforms. Can this be
    done in a portable way by arranging for auto-vectorization ? Something
    like commit 88709176236caf. This way it would benefit other platforms
    as well.
    
    I tried to compile the following code using -O3, and the assembly does
    have vectorized instructions.
    
    #include <stdio.h>
    int main()
    {
            int i;
            char s1[200] = "abcdewhruerhetr";
            char s2[200] = "oweurietiureuhtrethre";
            char s3[200] = {0};
    
        for (i = 0; i < sizeof(s1); i++)
        {
            s3[i] = s1[i] ^ s2[i];
        }
    
        printf("%s\n", s3);
    }
    
    
    
    
  20. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-03-09T11:43:52Z

    On Tue, Mar 9, 2021 at 5:00 AM Amit Khandekar <amitdkhan.pg@gmail.com>
    wrote:
    >
    > Hi,
    >
    > Just a quick question before I move on to review the patch ... The
    > improvement looks like it is only meant for x86 platforms.
    
    Actually it's meant to be faster for all platforms, since the C fallback is
    quite a bit different from HEAD. I've found it to be faster on ppc64le. An
    earlier version of the patch was a loser on 32-bit Arm because of alignment
    issues, but if you could run the test script attached to [1] on 64-bit Arm,
    I'd be curious to see how it does on 0002, and whether 0003 and 0004 make
    things better or worse. If there is trouble building on non-x86 platforms,
    I'd want to fix that also.
    
    (Note: 0001 is not my patch, and I just include it for the tests)
    
    > Can this be
    > done in a portable way by arranging for auto-vectorization ? Something
    > like commit 88709176236caf. This way it would benefit other platforms
    > as well.
    
    I'm fairly certain that the author of a compiler capable of doing that in
    this case would be eligible for some kind of AI prize. :-)
    
    [1]
    https://www.postgresql.org/message-id/06d45421-61b8-86dd-e765-f1ce527a5a2f@iki.fi
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  21. Re: [POC] verifying UTF-8 using SIMD instructions

    Amit Khandekar <amitdkhan.pg@gmail.com> — 2021-03-12T13:14:13Z

    On Tue, 9 Mar 2021 at 17:14, John Naylor <john.naylor@enterprisedb.com> wrote:
    > On Tue, Mar 9, 2021 at 5:00 AM Amit Khandekar <amitdkhan.pg@gmail.com> wrote:
    > > Just a quick question before I move on to review the patch ... The
    > > improvement looks like it is only meant for x86 platforms.
    >
    > Actually it's meant to be faster for all platforms, since the C fallback is quite a bit different from HEAD. I've found it to be faster on ppc64le. An earlier version of the patch was a loser on 32-bit Arm because of alignment issues, but if you could run the test script attached to [1] on 64-bit Arm, I'd be curious to see how it does on 0002, and whether 0003 and 0004 make things better or worse. If there is trouble building on non-x86 platforms, I'd want to fix that also.
    
    On my Arm64 VM :
    
    HEAD :
     mixed | ascii
    -------+-------
      1091 |   628
    (1 row)
    
    PATCHED :
     mixed | ascii
    -------+-------
       681 |   119
    
    So the fallback function does show improvements on Arm64.
    
    I guess, if at all we use the equivalent Arm NEON intrinsics, the
    "mixed" figures will be close to the "ascii" figures, going by your
    figures on x86.
    
    > > Can this be
    > > done in a portable way by arranging for auto-vectorization ? Something
    > > like commit 88709176236caf. This way it would benefit other platforms
    > > as well.
    >
    > I'm fairly certain that the author of a compiler capable of doing that in this case would be eligible for some kind of AI prize. :-)
    
    :)
    
    I was not thinking about auto-vectorizing the code in
    pg_validate_utf8_sse42(). Rather, I was considering auto-vectorization
    inside the individual helper functions that you wrote, such as
    _mm_setr_epi8(), shift_right(), bitwise_and(), prev1(), splat(),
    saturating_sub() etc. I myself am not sure whether it is feasible to
    write code that auto-vectorizes all these function definitions.
    saturating_sub() seems hard, but I could see the gcc docs mentioning
    support for generating such instructions for a particular code loop.
    But for the index lookup function() it seems impossible to generate
    the  needed index lookup intrinsics. We can have platform-specific
    function definitions for such exceptional cases.
    
    I am considering this only because that would make the exact code work
    on other platforms like arm64 and ppc, and won't have to have
    platform-specific files. But I understand that it is easier said than
    done. We will have to process the loop in pg_validate_utf8_sse42() in
    128-bit chunks, and pass each chunk to individual functions, which
    could mean extra work and extra copy in extracting the chunk data and
    passing it around, which may make things drastically slow. You are
    passing around the chunks using __m128i type, so perhaps it means
    passing around just a reference to the simd registers. Not sure.
    
    
    -- 
    Thanks,
    -Amit Khandekar
    Huawei Technologies
    
    
    
    
  22. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-03-12T15:36:51Z

    On Fri, Mar 12, 2021 at 9:14 AM Amit Khandekar <amitdkhan.pg@gmail.com>
    wrote:
    >
    > On my Arm64 VM :
    >
    > HEAD :
    >  mixed | ascii
    > -------+-------
    >   1091 |   628
    > (1 row)
    >
    > PATCHED :
    >  mixed | ascii
    > -------+-------
    >    681 |   119
    
    Thanks for testing! Good, the speedup is about as much as I can hope for
    using plain C. In the next patch I'll go ahead and squash in the ascii fast
    path, using 16-byte stride, unless there are objections. I claim we can
    live with the regression Heikki found on an old 32-bit Arm platform since
    it doesn't seem to be true of Arm in general.
    
    > I guess, if at all we use the equivalent Arm NEON intrinsics, the
    > "mixed" figures will be close to the "ascii" figures, going by your
    > figures on x86.
    
    I would assume so.
    
    > I was not thinking about auto-vectorizing the code in
    > pg_validate_utf8_sse42(). Rather, I was considering auto-vectorization
    > inside the individual helper functions that you wrote, such as
    > _mm_setr_epi8(), shift_right(), bitwise_and(), prev1(), splat(),
    
    If the PhD holders who came up with this algorithm thought it possible to
    do it that way, I'm sure they would have. In reality, simdjson has
    different files for SSE4, AVX, AVX512, NEON, and Altivec. We can
    incorporate any of those as needed. That's a PG15 project, though, and I'm
    not volunteering.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  23. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-03-19T19:24:06Z

    I wrote:
    
    > Thanks for testing! Good, the speedup is about as much as I can hope for
    using plain C. In the next patch I'll go ahead and squash in the ascii fast
    path, using 16-byte stride, unless there are objections. I claim we can
    live with the regression Heikki found on an old 32-bit Arm platform since
    it doesn't seem to be true of Arm in general.
    
    In v8, I've squashed the 16-byte stride into 0002. I also removed the sole
    holdout of hard-coded intrinsics, by putting _mm_setr_epi8 inside a
    variadic macro, and also did some reordering of the one-line function
    definitions. (As before, 0001 is not my patch, but parts of it are a
    prerequisite to my regressions tests).
    
    Over in [1] , I tested in-situ in a COPY FROM test and found a 10% speedup
    with mixed ascii and multibyte in the copy code, i.e. with buffer and
    storage taken completely out of the picture.
    
    [1]
    https://www.postgresql.org/message-id/CAFBsxsEybzagsrmuoLsKYx417Sce9cgnM91nf8f9HKGLadixPg%40mail.gmail.com
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  24. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-04-01T14:22:06Z

    v9 is just a rebase.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  25. speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-02T16:26:41Z

    For v10, I've split the patch up into two parts. 0001 uses pure C
    everywhere. This is much smaller and easier to review, and gets us the most
    bang for the buck.
    
    One concern Heikki raised upthread is that platforms with poor
    unaligned-memory access will see a regression. We could easily add an
    #ifdef to take care of that, but I haven't done so here.
    
    To recap: On ascii-only input with storage taken out of the picture,
    profiles of COPY FROM show a reduction from nealy 10% down to just over 1%.
    In microbenchmarks found earlier in this thread, this works out to about 7
    times faster. On multibyte/mixed input, 0001 is a bit faster, but not
    really enough to make a difference in copy performance.
    
    0002 adds the SSE4 implementation on x86-64, and is equally fast on all
    input, at the cost of greater complexity.
    
    To reflect the split, I've changed the thread subject and the commitfest
    title.
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com
    
  26. Re: speed up verifying UTF-8

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-06-03T13:15:59Z

    On 02/06/2021 19:26, John Naylor wrote:
    > For v10, I've split the patch up into two parts. 0001 uses pure C 
    > everywhere. This is much smaller and easier to review, and gets us the 
    > most bang for the buck.
    > 
    > One concern Heikki raised upthread is that platforms with poor 
    > unaligned-memory access will see a regression. We could easily add an 
    > #ifdef to take care of that, but I haven't done so here.
    > 
    > To recap: On ascii-only input with storage taken out of the picture, 
    > profiles of COPY FROM show a reduction from nealy 10% down to just over 
    > 1%. In microbenchmarks found earlier in this thread, this works out to 
    > about 7 times faster. On multibyte/mixed input, 0001 is a bit faster, 
    > but not really enough to make a difference in copy performance.
    
    Nice!
    
    This kind of bit-twiddling is fun, so I couldn't resist tinkering with 
    it, to see if we can shave some more instructions from it:
    
    > +/* from https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord */
    > +#define HAS_ZERO(chunk) ( \
    > +	((chunk) - UINT64CONST(0x0101010101010101)) & \
    > +	 ~(chunk) & \
    > +	 UINT64CONST(0x8080808080808080))
    > +
    > +/* Verify a chunk of bytes for valid ASCII including a zero-byte check. */
    > +static inline int
    > +check_ascii(const unsigned char *s, int len)
    > +{
    > +	uint64		half1,
    > +				half2,
    > +				highbits_set;
    > +
    > +	if (len >= 2 * sizeof(uint64))
    > +	{
    > +		memcpy(&half1, s, sizeof(uint64));
    > +		memcpy(&half2, s + sizeof(uint64), sizeof(uint64));
    > +
    > +		/* If there are zero bytes, bail and let the slow path handle it. */
    > +		if (HAS_ZERO(half1) || HAS_ZERO(half2))
    > +			return 0;
    > +
    > +		/* Check if any bytes in this chunk have the high bit set. */
    > +		highbits_set = ((half1 | half2) & UINT64CONST(0x8080808080808080));
    > +
    > +		if (!highbits_set)
    > +			return 2 * sizeof(uint64);
    > +		else
    > +			return 0;
    > +	}
    > +	else
    > +		return 0;
    > +}
    
    Some ideas:
    
    1. Better to check if any high bits are set first. We care more about 
    the speed of that than of detecting zero bytes, because input with high 
    bits is valid but zeros are an error.
    
    2. Since we check that there are no high bits, we can do the zero-checks 
    with fewer instructions like this:
    
    /* NB: this is only correct if 'chunk' doesn't have any high bits set */
    #define HAS_ZERO(chunk) ( \
       ((chunk) + \
        UINT64CONST(0x7f7f7f7f7f7f7f7f)) & \
       UINT64CONST(0x8080808080808080) == UINT64CONST(0x8080808080808080))
    
    3. It's probably cheaper perform the HAS_ZERO check just once on (half1 
    | half2). We have to compute (half1 | half2) anyway.
    
    
    Putting all that together:
    
    /* Verify a chunk of bytes for valid ASCII including a zero-byte check. */
    static inline int
    check_ascii(const unsigned char *s, int len)
    {
    	uint64		half1,
    				half2,
    				highbits_set;
    	uint64		x;
    
    	if (len >= 2 * sizeof(uint64))
    	{
    		memcpy(&half1, s, sizeof(uint64));
    		memcpy(&half2, s + sizeof(uint64), sizeof(uint64));
    
    		/* Check if any bytes in this chunk have the high bit set. */
    		highbits_set = ((half1 | half2) & UINT64CONST(0x8080808080808080));
    		if (highbits_set)
    			return 0;
    
    		/*
    		 * Check if there are any zero bytes in this chunk. This is only correct
    		 * if there are no high bits set, but we checked that already.
    		 */
    		x = (half1 | half2) + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    		x &= UINT64CONST(0x8080808080808080);
    		if (x != UINT64CONST(0x8080808080808080))
    			return 0;
    
    		return 2 * sizeof(uint64);
    	}
    	else
    		return 0;
    }
    
    In quick testing, that indeed compiles into fewer instructions. With 
    GCC, there's no measurable difference in performance. But with clang, 
    this version is much faster than the original, because the original 
    version is much slower than when compiled with GCC. In other words, this 
    version seems to avoid some clang misoptimization. I tested only with 
    ASCII input, I haven't tried other cases.
    
    What test set have you been using for performance testing this? I'd like 
    to know how this version compares, and I could also try running it on my 
    old raspberry pi, which is more strict about alignmnt.
    
    > 0002 adds the SSE4 implementation on x86-64, and is equally fast on all 
    > input, at the cost of greater complexity.
    
    Didn't look closely, but seems reasonable at a quick glance.
    
    - Heikki
    
    
    
    
  27. Re: speed up verifying UTF-8

    Greg Stark <stark@mit.edu> — 2021-06-03T14:33:59Z

    > 3. It's probably cheaper perform the HAS_ZERO check just once on (half1
    | half2). We have to compute (half1 | half2) anyway.
    
    Wouldn't you have to check (half1 & half2) ?
    
    
    
    
  28. Re: speed up verifying UTF-8

    Greg Stark <stark@mit.edu> — 2021-06-03T14:41:41Z

    I haven't looked at the surrounding code. Are we processing all the
    COPY data in one long stream or processing each field individually? If
    we're processing much more than 128 bits and happy to detect NUL
    errors only at the end after wasting some work then you could hoist
    that has_zero check entirely out of the loop (removing the branch
    though it's probably a correctly predicted branch anyways).
    
    Do something like:
    
    zero_accumulator = zero_accumulator & next_chunk
    
    in the loop and then only at the very end check for zeros in that.
    
    
    
    
  29. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-03T15:33:21Z

    On Thu, Jun 3, 2021 at 10:42 AM Greg Stark <stark@mit.edu> wrote:
    >
    > I haven't looked at the surrounding code. Are we processing all the
    > COPY data in one long stream or processing each field individually?
    
    It happens on 64kB chunks.
    
    > If
    > we're processing much more than 128 bits and happy to detect NUL
    > errors only at the end after wasting some work then you could hoist
    > that has_zero check entirely out of the loop (removing the branch
    > though it's probably a correctly predicted branch anyways).
    >
    > Do something like:
    >
    > zero_accumulator = zero_accumulator & next_chunk
    >
    > in the loop and then only at the very end check for zeros in that.
    
    That's the approach taken in the SSE4 patch, and in fact that's the logical
    way to do it there. I hadn't considered doing it that way in the pure C
    case, but I think it's worth trying.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  30. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-03T15:42:51Z

    I wrote:
    
    > On Thu, Jun 3, 2021 at 10:42 AM Greg Stark <stark@mit.edu> wrote:
    > >
    
    > > If
    > > we're processing much more than 128 bits and happy to detect NUL
    > > errors only at the end after wasting some work then you could hoist
    > > that has_zero check entirely out of the loop (removing the branch
    > > though it's probably a correctly predicted branch anyways).
    > >
    > > Do something like:
    > >
    > > zero_accumulator = zero_accumulator & next_chunk
    > >
    > > in the loop and then only at the very end check for zeros in that.
    >
    > That's the approach taken in the SSE4 patch, and in fact that's the
    logical way to do it there. I hadn't considered doing it that way in the
    pure C case, but I think it's worth trying.
    
    Actually, I spoke too quickly. We can't have an error accumulator in the C
    case because we need to return how many bytes were valid. In fact, in the
    SSE case, it checks the error vector at the end and then reruns with the
    fallback case to count the valid bytes.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  31. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-03T18:58:57Z

    On Thu, Jun 3, 2021 at 9:16 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    
    > Some ideas:
    >
    > 1. Better to check if any high bits are set first. We care more about
    > the speed of that than of detecting zero bytes, because input with high
    > bits is valid but zeros are an error.
    >
    > 2. Since we check that there are no high bits, we can do the zero-checks
    > with fewer instructions like this:
    
    Both ideas make sense, and I like the shortcut we can take with the zero
    check. I think Greg is right that the zero check needs “half1 & half2”, so
    I tested with that (updated patches attached).
    
    > What test set have you been using for performance testing this? I'd like
    
    The microbenchmark is the same one you attached to [1], which I extended
    with a 95% multibyte case. With the new zero check:
    
    clang 12.0.5 / MacOS:
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
         981 |   688 |   371
    
    0001:
    
     chinese | mixed | ascii
    ---------+-------+-------
         932 |   548 |   110
    
    plus optimized zero check:
    
     chinese | mixed | ascii
    ---------+-------+-------
         689 |   573 |    59
    
    It makes sense that the Chinese text case is faster since the zero check is
    skipped.
    
    gcc 4.8.5 / Linux:
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
        2561 |  1493 |   825
    
    0001:
    
     chinese | mixed | ascii
    ---------+-------+-------
        2968 |  1035 |   158
    
    plus optimized zero check:
    
     chinese | mixed | ascii
    ---------+-------+-------
        2413 |  1078 |   137
    
    The second machine is a bit older and has an old compiler, but there is
    still a small speed increase. In fact, without Heikki's tweaks, 0001
    regresses on multibyte.
    
    (Note: I'm not seeing the 7x improvement I claimed for 0001 here, but that
    was from memory and I think that was a different machine and newer gcc. We
    can report a range of results as we proceed.)
    
    [1]
    https://www.postgresql.org/message-id/06d45421-61b8-86dd-e765-f1ce527a5a2f@iki.fi
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  32. Re: speed up verifying UTF-8

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-06-03T19:08:57Z

    On 03/06/2021 17:33, Greg Stark wrote:
    >> 3. It's probably cheaper perform the HAS_ZERO check just once on (half1
    > | half2). We have to compute (half1 | half2) anyway.
    > 
    > Wouldn't you have to check (half1 & half2) ?
    
    Ah, you're right of course. But & is not quite right either, it will 
    give false positives. That's ok from a correctness point of view here, 
    because we then fall back to checking byte by byte, but I don't think 
    it's a good tradeoff.
    
    I think this works, however:
    
    /* Verify a chunk of bytes for valid ASCII including a zero-byte check. */
    static inline int
    check_ascii(const unsigned char *s, int len)
    {
    	uint64		half1,
    				half2,
    				highbits_set;
    	uint64		x1,
    				x2;
    	uint64		x;
    
    	if (len >= 2 * sizeof(uint64))
    	{
    		memcpy(&half1, s, sizeof(uint64));
    		memcpy(&half2, s + sizeof(uint64), sizeof(uint64));
    
    		/* Check if any bytes in this chunk have the high bit set. */
    		highbits_set = ((half1 | half2) & UINT64CONST(0x8080808080808080));
    		if (highbits_set)
    			return 0;
    
    		/*
    		 * Check if there are any zero bytes in this chunk.
    		 *
    		 * First, add 0x7f to each byte. This sets the high bit in each byte,
    		 * unless it was a zero. We already checked that none of the bytes had
    		 * the high bit set previously, so the max value each byte can have
    		 * after the addition is 0x7f + 0x7f = 0xfe, and we don't need to
    		 * worry about carrying over to the next byte.
    		 */
    		x1 = half1 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    		x2 = half2 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    
    		/* then check that the high bit is set in each byte. */
    		x = (x1 | x2);
    		x &= UINT64CONST(0x8080808080808080);
    		if (x != UINT64CONST(0x8080808080808080))
    			return 0;
    
    		return 2 * sizeof(uint64);
    	}
    	else
    		return 0;
    }
    
    - Heikki
    
    
    
    
  33. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-03T19:10:35Z

    On Thu, Jun 3, 2021 at 3:08 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > On 03/06/2021 17:33, Greg Stark wrote:
    > >> 3. It's probably cheaper perform the HAS_ZERO check just once on (half1
    > > | half2). We have to compute (half1 | half2) anyway.
    > >
    > > Wouldn't you have to check (half1 & half2) ?
    >
    > Ah, you're right of course. But & is not quite right either, it will
    > give false positives. That's ok from a correctness point of view here,
    > because we then fall back to checking byte by byte, but I don't think
    > it's a good tradeoff.
    
    Ah, of course.
    
    >                 /*
    >                  * Check if there are any zero bytes in this chunk.
    >                  *
    >                  * First, add 0x7f to each byte. This sets the high bit
    in each byte,
    >                  * unless it was a zero. We already checked that none of
    the bytes had
    >                  * the high bit set previously, so the max value each
    byte can have
    >                  * after the addition is 0x7f + 0x7f = 0xfe, and we don't
    need to
    >                  * worry about carrying over to the next byte.
    >                  */
    >                 x1 = half1 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    >                 x2 = half2 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    >
    >                 /* then check that the high bit is set in each byte. */
    >                 x = (x1 | x2);
    >                 x &= UINT64CONST(0x8080808080808080);
    >                 if (x != UINT64CONST(0x8080808080808080))
    >                         return 0;
    
    That seems right, I'll try that and update the patch. (Forgot to attach
    earlier anyway)
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  34. Re: speed up verifying UTF-8

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-06-03T19:16:04Z

    On 03/06/2021 22:10, John Naylor wrote:
    > On Thu, Jun 3, 2021 at 3:08 PM Heikki Linnakangas <hlinnaka@iki.fi 
    > <mailto:hlinnaka@iki.fi>> wrote:
    >  >                 x1 = half1 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    >  >                 x2 = half2 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    >  >
    >  >                 /* then check that the high bit is set in each byte. */
    >  >                 x = (x1 | x2);
    >  >                 x &= UINT64CONST(0x8080808080808080);
    >  >                 if (x != UINT64CONST(0x8080808080808080))
    >  >                         return 0;
    > 
    > That seems right, I'll try that and update the patch. (Forgot to attach 
    > earlier anyway)
    
    Ugh, actually that has the same issue as before. If one of the bytes is 
    in one half is zero, but not in the other half, this fail to detect it. 
    Sorry for the noise..
    
    - Heikki
    
    
    
    
  35. Re: speed up verifying UTF-8

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-06-03T19:22:15Z

    On 03/06/2021 22:16, Heikki Linnakangas wrote:
    > On 03/06/2021 22:10, John Naylor wrote:
    >> On Thu, Jun 3, 2021 at 3:08 PM Heikki Linnakangas <hlinnaka@iki.fi
    >> <mailto:hlinnaka@iki.fi>> wrote:
    >>   >                 x1 = half1 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    >>   >                 x2 = half2 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    >>   >
    >>   >                 /* then check that the high bit is set in each byte. */
    >>   >                 x = (x1 | x2);
    >>   >                 x &= UINT64CONST(0x8080808080808080);
    >>   >                 if (x != UINT64CONST(0x8080808080808080))
    >>   >                         return 0;
    >>
    >> That seems right, I'll try that and update the patch. (Forgot to attach
    >> earlier anyway)
    > 
    > Ugh, actually that has the same issue as before. If one of the bytes is
    > in one half is zero, but not in the other half, this fail to detect it.
    > Sorry for the noise..
    
    If you replace (x1 | x2) with (x1 & x2) above, I think it's correct.
    
    - Heikki
    
    
    
    
  36. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-06T19:21:51Z

    On Thu, Jun 3, 2021 at 3:22 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > On 03/06/2021 22:16, Heikki Linnakangas wrote:
    > > On 03/06/2021 22:10, John Naylor wrote:
    > >> On Thu, Jun 3, 2021 at 3:08 PM Heikki Linnakangas <hlinnaka@iki.fi
    > >> <mailto:hlinnaka@iki.fi>> wrote:
    > >>   >                 x1 = half1 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    > >>   >                 x2 = half2 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    > >>   >
    > >>   >                 /* then check that the high bit is set in each
    byte. */
    > >>   >                 x = (x1 | x2);
    > >>   >                 x &= UINT64CONST(0x8080808080808080);
    > >>   >                 if (x != UINT64CONST(0x8080808080808080))
    > >>   >                         return 0;
    
    > If you replace (x1 | x2) with (x1 & x2) above, I think it's correct.
    
    After looking at it again with fresh eyes, I agree this is correct. I
    modified the regression tests to pad the input bytes with ascii so that the
    code path that works on 16-bytes at a time is tested. I use both UTF-8
    input tables for some of the additional tests. There is a de facto
    requirement that the descriptions are unique across both of the input
    tables. That could be done more elegantly, but I wanted to keep things
    simple for now.
    
    v11-0001 is an improvement over v10:
    
    clang 12.0.5 / MacOS:
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
         975 |   686 |   369
    
    v10-0001:
    
     chinese | mixed | ascii
    ---------+-------+-------
         930 |   549 |   109
    
    v11-0001:
    
     chinese | mixed | ascii
    ---------+-------+-------
         687 |   440 |    64
    
    
    gcc 4.8.5 / Linux (older machine)
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
        2559 |  1495 |   825
    
    v10-0001:
    
     chinese | mixed | ascii
    ---------+-------+-------
        2966 |  1034 |   156
    
    v11-0001:
    
     chinese | mixed | ascii
    ---------+-------+-------
        2242 |   824 |   140
    
    Previous testing on POWER8 and Arm64 leads me to expect similar results
    there as well.
    
    I also looked again at 0002 and decided I wasn't quite happy with the test
    coverage. Previously, the code padded out a short input with ascii so that
    the 16-bytes-at-a-time code path was always exercised. However, that
    required some finicky complexity and still wasn't adequate. For v11, I
    ripped that out and put the responsibility on the regression tests to make
    sure the various code paths are exercised.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  37. Re: speed up verifying UTF-8

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-06-07T12:24:53Z

    On 03/06/2021 21:58, John Naylor wrote:
    > 
    >  > What test set have you been using for performance testing this? I'd like
    > 
    > The microbenchmark is the same one you attached to [1], which I extended 
    > with a 95% multibyte case.
    
    Could you share the exact test you're using? I'd like to test this on my 
    old raspberry pi, out of curiosity.
    
    - Heikki
    
    
    
    
  38. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-07T12:39:40Z

    On Mon, Jun 7, 2021 at 8:24 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    >
    > On 03/06/2021 21:58, John Naylor wrote:
    > > The microbenchmark is the same one you attached to [1], which I extended
    > > with a 95% multibyte case.
    >
    > Could you share the exact test you're using? I'd like to test this on my
    > old raspberry pi, out of curiosity.
    
    Sure, attached.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  39. Re: speed up verifying UTF-8

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-06-09T11:02:02Z

    On 07/06/2021 15:39, John Naylor wrote:
    > On Mon, Jun 7, 2021 at 8:24 AM Heikki Linnakangas <hlinnaka@iki.fi 
    > <mailto:hlinnaka@iki.fi>> wrote:
    >  >
    >  > On 03/06/2021 21:58, John Naylor wrote:
    >  > > The microbenchmark is the same one you attached to [1], which I 
    > extended
    >  > > with a 95% multibyte case.
    >  >
    >  > Could you share the exact test you're using? I'd like to test this on my
    >  > old raspberry pi, out of curiosity.
    > 
    > Sure, attached.
    > 
    > --
    > John Naylor
    > EDB: http://www.enterprisedb.com <http://www.enterprisedb.com>
    > 
    Results from chipmunk, my first generation Raspberry Pi:
    
    Master:
    
      chinese | mixed | ascii
    ---------+-------+-------
        25392 | 16287 | 10295
    (1 row)
    
    v11-0001-Rewrite-pg_utf8_verifystr-for-speed.patch:
    
      chinese | mixed | ascii
    ---------+-------+-------
        17739 | 10854 |  4121
    (1 row)
    
    So that's good.
    
    What is the worst case scenario for this algorithm? Something where the 
    new fast ASCII check never helps, but is as fast as possible with the 
    old code. For that, I added a repeating pattern of '123456789012345ä' to 
    the test set (these results are from my Intel laptop, not the raspberry pi):
    
    Master:
    
      chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
         1333 |   757 |   410 |    573
    (1 row)
    
    v11-0001-Rewrite-pg_utf8_verifystr-for-speed.patch:
    
      chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
          942 |   470 |    66 |   1249
    (1 row)
    
    So there's a regression with that input. Maybe that's acceptable, this 
    is the worst case, after all. Or you could tweak check_ascii for a 
    different performance tradeoff, by checking the two 64-bit words 
    separately and returning "8" if the failure happens in the second word. 
    And I haven't tried the SSE patch yet, maybe that compensates for this.
    
    - Heikki
    
    
    
    
  40. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-10T12:45:01Z

    On Wed, Jun 9, 2021 at 7:02 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    > What is the worst case scenario for this algorithm? Something where the
    > new fast ASCII check never helps, but is as fast as possible with the
    > old code. For that, I added a repeating pattern of '123456789012345ä' to
    > the test set (these results are from my Intel laptop, not the raspberry
    pi):
    >
    > Master:
    >
    >   chinese | mixed | ascii | mixed2
    > ---------+-------+-------+--------
    >      1333 |   757 |   410 |    573
    > (1 row)
    >
    > v11-0001-Rewrite-pg_utf8_verifystr-for-speed.patch:
    >
    >   chinese | mixed | ascii | mixed2
    > ---------+-------+-------+--------
    >       942 |   470 |    66 |   1249
    > (1 row)
    
    I get a much smaller regression on my laptop with clang 12:
    
    master:
    
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
         978 |   685 |   370 |    452
    
    v11-0001:
    
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
         686 |   438 |    64 |    595
    
    > So there's a regression with that input. Maybe that's acceptable, this
    > is the worst case, after all. Or you could tweak check_ascii for a
    > different performance tradeoff, by checking the two 64-bit words
    > separately and returning "8" if the failure happens in the second word.
    
    For v12 (unformatted and without 0002 rebased) I tried the following:
    --
    highbits_set = (half1) & UINT64CONST(0x8080808080808080);
    if (highbits_set)
         return 0;
    
    x1 = half1 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    x1 &= UINT64CONST(0x8080808080808080);
    if (x1 != UINT64CONST(0x8080808080808080))
         return 0;
    
    /* now we know we have at least 8 bytes of valid ascii, so if any of these
    tests fails, return that */
    
    highbits_set = (half2) & UINT64CONST(0x8080808080808080);
    if (highbits_set)
         return sizeof(uint64);
    
    x2 = half2 + UINT64CONST(0x7f7f7f7f7f7f7f7f);
    x2 &= UINT64CONST(0x8080808080808080);
    if (x2 != UINT64CONST(0x8080808080808080))
         return sizeof(uint64);
    
    return 2 * sizeof(uint64);
    --
    and got this:
    
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
         674 |   499 |   170 |    421
    
    Pure ascii is significantly slower, but the regression is gone.
    
    I used the string repeat('123456789012345ä', 3647) to match the ~62000
    bytes in the other strings (62000 / 17 = 3647)
    
    > And I haven't tried the SSE patch yet, maybe that compensates for this.
    
    I would expect that this case is identical to all-multibyte. The worst case
    for SSE might be alternating 16-byte chunks of ascii-only and chunks of
    multibyte, since that's one of the few places it branches. In simdjson,
    they check ascii on 64 byte blocks at a time ((c1 | c2) | (c3 | c4)) and
    check only the previous block's "chunk 4" for incomplete sequences at the
    end. It's a bit messier, so I haven't done it, but it's an option.
    
    Also, if SSE is accepted into the tree, then the C fallback is only
    important on platforms like PowerPC64 and Arm64, so we can make
    the tradeoff by testing those more carefully. I'll test on PowerPC soon.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  41. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-11T00:36:14Z

    I wrote:
    
    > Also, if SSE is accepted into the tree, then the C fallback is only
    important on platforms like PowerPC64 and Arm64, so we can make the
    tradeoff by testing those more carefully. I'll test on PowerPC soon.
    
    I got around to testing on POWER8 / Linux / gcc 4.8.5 and found a
    regression in the mixed2 case in v11. v12 improves that at the cost of some
    improvement in the ascii case (5x vs. 8x).
    
    master:
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
        2966 |  1525 |   871 |   1474
    
    v11-0001:
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
        1030 |   644 |   102 |   1760
    
    v12-0001:
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
         977 |   632 |   168 |   1113
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  42. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-29T11:20:38Z

    I still wasn't quite happy with the churn in the regression tests, so for
    v13 I gave up on using both the existing utf8 table and my new one for the
    "padded input" tests, and instead just copied the NUL byte test into the
    new table. Also added a primary key to make sure the padded test won't give
    weird results if a new entry has a duplicate description.
    
    I came up with "highbit_carry" as a more descriptive variable name than
    "x", but that doesn't matter a whole lot.
    
    It also occurred to me that if we're going to check one 8-byte chunk at a
    time (like v12 does), maybe it's only worth it to load 8 bytes at a time.
    An earlier version did this, but without the recent tweaks. The worst-case
    scenario now might be different from the one with 16-bytes, but for now
    just tested the previous worst case (mixed2). Only tested on ppc64le, since
    I'm hoping x86 will get the SIMD algorithm (I'm holding off rebasing 0002
    until 0001 settles down).
    
    Power8, Linux, gcc 4.8
    
    master:
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
        2952 |  1520 |   871 |   1473
    
    v11:
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
        1015 |   641 |   102 |   1636
    
    v12:
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
         964 |   629 |   168 |   1069
    
    v13:
     chinese | mixed | ascii | mixed2
    ---------+-------+-------+--------
         954 |   643 |   202 |   1046
    
    v13 is not that much different from v12, but has the nice property of
    simpler code. Both are not as nice as v11 for ascii, but don't regress for
    the latter's worst case. I'm leaning towards v13 for the fallback.
    
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com
    
  43. Re: speed up verifying UTF-8

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-06-30T11:18:32Z

    On 29/06/2021 14:20, John Naylor wrote:
    > I still wasn't quite happy with the churn in the regression tests, so 
    > for v13 I gave up on using both the existing utf8 table and my new one 
    > for the "padded input" tests, and instead just copied the NUL byte test 
    > into the new table. Also added a primary key to make sure the padded 
    > test won't give weird results if a new entry has a duplicate description.
    > 
    > I came up with "highbit_carry" as a more descriptive variable name than 
    > "x", but that doesn't matter a whole lot.
    > 
    > It also occurred to me that if we're going to check one 8-byte chunk at 
    > a time (like v12 does), maybe it's only worth it to load 8 bytes at a 
    > time. An earlier version did this, but without the recent tweaks. The 
    > worst-case scenario now might be different from the one with 16-bytes, 
    > but for now just tested the previous worst case (mixed2).
    
    I tested the new worst case scenario on my laptop:
    
    gcc master:
    
      chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         1311 |   758 |   405 |     583 |    725
    
    
    gcc v13:
    
      chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
          956 |   472 |   160 |     572 |    939
    
    
    mixed16 is the same as "mixed2" in the previous rounds, with 
    '123456789012345ä' as the repeating string, and mixed8 uses '1234567ä', 
    which I believe is the worst case for patch v13. So v13 is somewhat 
    slower than master in the worst case.
    
    Hmm, there's one more simple trick we can do: We can have a separate 
    fast-path version of the loop when there are at least 8 bytes of input 
    left, skipping all the length checks. With that:
    
    gcc v14:
      chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
          737 |   412 |    94 |     476 |    725
    
    
    All the above numbers were with gcc 10.2.1. For completeness, with clang 
    11.0.1-2 I got:
    
    clang master:
      chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         1044 |   724 |   403 |     930 |    603
    (1 row)
    
    clang v13:
      chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
          596 |   445 |    79 |     417 |    715
    (1 row)
    
    
    clang v14:
      chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
          600 |   337 |    93 |     318 |    511
    
    Attached is patch v14 with that optimization. It needs some cleanup, I 
    just hacked it up quickly for performance testing.
    
    - Heikki
    
  44. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-06-30T16:54:23Z

    On Wed, Jun 30, 2021 at 7:18 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    
    > Hmm, there's one more simple trick we can do: We can have a separate
    > fast-path version of the loop when there are at least 8 bytes of input
    > left, skipping all the length checks. With that:
    
    Good idea, and the numbers look good on Power8 / gcc 4.8 as well:
    
    master:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        2951 |  1521 |   871 |    1473 |   1508
    
    v13:
    
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         949 |   642 |   203 |    1046 |   1818
    
    v14:
    
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         887 |   607 |   179 |     776 |   1325
    
    
    I don't think the new structuring will pose any challenges for rebasing
    0002, either. This might need some experimentation, though:
    
    + * Subroutine of pg_utf8_verifystr() to check on char. Returns the length
    of the
    + * character at *s in bytes, or 0 on invalid input or premature end of
    input.
    + *
    + * XXX: could this be combined with pg_utf8_verifychar above?
    + */
    +static inline int
    +pg_utf8_verify_one(const unsigned char *s, int len)
    
    It seems like it would be easy to have pg_utf8_verify_one in my proposed
    pg_utf8.h header and replace the body of pg_utf8_verifychar with it.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  45. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-12T19:45:39Z

    I wrote:
    
    > I don't think the new structuring will pose any challenges for rebasing
    0002, either. This might need some experimentation, though:
    >
    > + * Subroutine of pg_utf8_verifystr() to check on char. Returns the
    length of the
    > + * character at *s in bytes, or 0 on invalid input or premature end of
    input.
    > + *
    > + * XXX: could this be combined with pg_utf8_verifychar above?
    > + */
    > +static inline int
    > +pg_utf8_verify_one(const unsigned char *s, int len)
    >
    > It seems like it would be easy to have pg_utf8_verify_one in my proposed
    pg_utf8.h header and replace the body of pg_utf8_verifychar with it.
    
    0001: I went ahead and tried this for v15, and also attempted some clean-up:
    
    - Rename pg_utf8_verify_one to pg_utf8_verifychar_internal.
    - Have pg_utf8_verifychar_internal return -1 for invalid input to match
    other functions in the file. We could also do this for check_ascii, but
    it's not quite the same thing, because the string could still have valid
    bytes in it, just not enough to advance the pointer by the stride length.
    - Remove hard-coded numbers (not wedded to this).
    
    - Use a call to pg_utf8_verifychar in the slow path.
    - Reduce pg_utf8_verifychar to thin wrapper around
    pg_utf8_verifychar_internal.
    
    The last two aren't strictly necessary, but it prevents bloating the binary
    in the slow path, and aids readability. For 0002, this required putting
    pg_utf8_verifychar* in src/port. (While writing this I noticed I neglected
    to explain that with a comment, though)
    
    Feedback welcome on any of the above.
    
    Since by now it hardly resembles the simdjson (or Fuchsia for that matter)
    fallback that it took inspiration from, I've removed that mention from the
    commit message.
    
    0002: Just a rebase to work with the above. One possible review point: We
    don't really need to have separate control over whether to use special
    instructions for CRC and UTF-8. It should probably be just one configure
    knob, but having them separate is perhaps easier to review.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  46. Re: speed up verifying UTF-8

    Amit Khandekar <amitdkhan.pg@gmail.com> — 2021-07-15T05:09:48Z

    On Tue, 13 Jul 2021 at 01:15, John Naylor <john.naylor@enterprisedb.com> wrote:
    > > It seems like it would be easy to have pg_utf8_verify_one in my proposed pg_utf8.h header and replace the body of pg_utf8_verifychar with it.
    >
    > 0001: I went ahead and tried this for v15, and also attempted some clean-up:
    >
    > - Rename pg_utf8_verify_one to pg_utf8_verifychar_internal.
    > - Have pg_utf8_verifychar_internal return -1 for invalid input to match other functions in the file. We could also do this for check_ascii, but it's not quite the same thing, because the string could still have valid bytes in it, just not enough to advance the pointer by the stride length.
    > - Remove hard-coded numbers (not wedded to this).
    >
    > - Use a call to pg_utf8_verifychar in the slow path.
    > - Reduce pg_utf8_verifychar to thin wrapper around pg_utf8_verifychar_internal.
    
    - check_ascii() seems to be used only for 64-bit chunks. So why not
    remove the len argument and the len <= sizeof(int64) checks inside the
    function. We can rename it to check_ascii64() for clarity.
    
    - I was thinking, why not have a pg_utf8_verify64() that processes
    64-bit chunks (or a 32-bit version). In check_ascii(), we anyway
    extract a 64-bit chunk from the string. We can use the same chunk to
    extract the required bits from a two byte char or a 4 byte char. This
    way we can avoid extraction of separate bytes like b1 = *s; b2 = s[1]
    etc. More importantly, we can avoid the separate continuation-char
    checks for each individual byte. Additionally, we can try to simplify
    the subsequent overlong or surrogate char checks. Something like this
    :
    
    int pg_utf8_verifychar_32(uint32 chunk)
    {
        int        len, l;
    
        for (len = sizeof(chunk); len > 0; (len -= l), (chunk = chunk << l))
        {
            /* Is 2-byte lead */
            if ((chunk & 0xF0000000) == 0xC0000000)
            {
                l = 2;
                /* .......  .......  */
            }
            /* Is 3-byte lead */
            else if ((chunk & 0xF0000000) == 0xE0000000)
            {
                l = 3;
                if (len < l)
                    break;
    
                /* b2 and b3 should be continuation bytes */
                if ((chunk & 0x00C0C000) != 0x00808000)
                    return sizeof(chunk) - len;
    
                switch (chunk & 0xFF200000)
                {
                    /* check 3-byte overlong: 1110.0000 1001.xxxx 10xx.xxxx
                     * i.e. (b1 == 0xE0 && b2 < 0xA0). We already know b2
    is of the form
                     * 10xx since it's a continuation char. Additionally
    condition b2 <=
                     * 0x9F means it is of the form 100x.xxxx.  i.e.
    either 1000.xxxx
                     * or 1001.xxxx. So just verify that it is xx0x.xxxx
                     */
                    case 0xE0000000:
                        return sizeof(chunk) - len;
    
                    /* check surrogate: 1110.1101 101x.xxxx 10xx.xxxx
                     * i.e. (b1 == 0xED && b2 > 0x9F): Here, > 0x9F means either
                     * 1010.xxxx, 1011.xxxx, 1100.xxxx, or 1110.xxxx. Last
    two are not
                     * possible because b2 is a continuation char. So it has to be
                     * first two. So just verify that it is xx1x.xxxx
                     */
                    case 0xED200000:
                        return sizeof(chunk) - len;
                    default:
                        ;
                }
    
            }
            /* Is 4-byte lead */
            else if ((chunk & 0xF0000000) == 0xF0000000)
            {
                /* .........  */
                l = 4;
            }
            else
                return sizeof(chunk) - len;
        }
        return sizeof(chunk) - len;
    }
    
    
    
    
  47. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-15T18:12:43Z

    On Thu, Jul 15, 2021 at 1:10 AM Amit Khandekar <amitdkhan.pg@gmail.com>
    wrote:
    
    > - check_ascii() seems to be used only for 64-bit chunks. So why not
    > remove the len argument and the len <= sizeof(int64) checks inside the
    > function. We can rename it to check_ascii64() for clarity.
    
    Thanks for taking a look!
    
    Well yes, but there's nothing so intrinsic to 64 bits that the name needs
    to reflect that. Earlier versions worked on 16 bytes at time. The compiler
    will optimize away the len check, but we could replace with an assert
    instead.
    
    > - I was thinking, why not have a pg_utf8_verify64() that processes
    > 64-bit chunks (or a 32-bit version). In check_ascii(), we anyway
    > extract a 64-bit chunk from the string. We can use the same chunk to
    > extract the required bits from a two byte char or a 4 byte char. This
    > way we can avoid extraction of separate bytes like b1 = *s; b2 = s[1]
    > etc.
    
    Loading bytes from L1 is really fast -- I wouldn't even call it
    "extraction".
    
    > More importantly, we can avoid the separate continuation-char
    > checks for each individual byte.
    
    On a pipelined superscalar CPU, I wouldn't expect it to matter in the
    slightest.
    
    > Additionally, we can try to simplify
    > the subsequent overlong or surrogate char checks. Something like this
    
    My recent experience with itemptrs has made me skeptical of this kind of
    thing, but the idea was interesting enough that I couldn't resist trying it
    out. I have two attempts, which are attached as v16*.txt and apply
    independently. They are rough, and some comments are now lies. To simplify
    the constants, I do shift down to uint32, and I didn't bother working
    around that. v16alpha regressed on worst-case input, so for v16beta I went
    back to earlier coding for the one-byte ascii check. That helped, but it's
    still slower than v14.
    
    That was not unexpected, but I was mildly shocked to find out that v15 is
    also slower than the v14 that Heikki posted. The only non-cosmetic
    difference is using pg_utf8_verifychar_internal within pg_utf8_verifychar.
    I'm not sure why it would make such a big difference here. The numbers on
    Power8 / gcc 4.8 (little endian):
    
    HEAD:
    
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        2951 |  1521 |   871 |    1474 |   1508
    
    v14:
    
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         885 |   607 |   179 |     774 |   1325
    
    v15:
    
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1085 |   671 |   180 |    1032 |   1799
    
    v16alpha:
    
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1268 |   822 |   180 |    1410 |   2518
    
    v16beta:
    
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1096 |   654 |   182 |     814 |   1403
    
    
    As it stands now, for v17 I'm inclined to go back to v15, but without the
    attempt at being clever that seems to have slowed it down from v14.
    
    Any interest in testing on 64-bit Arm?
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  48. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-15T22:00:05Z

    I wrote:
    
    > To simplify the constants, I do shift down to uint32, and I didn't bother
    working around that. v16alpha regressed on worst-case input, so for v16beta
    I went back to earlier coding for the one-byte ascii check. That helped,
    but it's still slower than v14.
    
    It occurred to me that I could rewrite the switch test into simple
    comparisons, like I already had for the 2- and 4-byte lead cases. While at
    it, I folded the leading byte and continuation tests into a single
    operation, like this:
    
    /* 3-byte lead with two continuation bytes */
    else if ((chunk & 0xF0C0C00000000000) == 0xE080800000000000)
    
    ...and also tried using 64-bit constants to avoid shifting. Still didn't
    quite beat v14, but got pretty close:
    
    > The numbers on Power8 / gcc 4.8 (little endian):
    >
    > HEAD:
    >
    >  chinese | mixed | ascii | mixed16 | mixed8
    > ---------+-------+-------+---------+--------
    >     2951 |  1521 |   871 |    1474 |   1508
    >
    > v14:
    >
    >  chinese | mixed | ascii | mixed16 | mixed8
    > ---------+-------+-------+---------+--------
    >      885 |   607 |   179 |     774 |   1325
    
    v16gamma:
    
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         952 |   632 |   180 |     800 |   1333
    
    A big-endian 64-bit platform just might shave enough cycles to beat v14
    this way... or not.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  49. Re: speed up verifying UTF-8

    Vladimir Sitnikov <sitnikov.vladimir@gmail.com> — 2021-07-16T05:44:06Z

    Have you considered shift-based DFA for a portable implementation
    https://gist.github.com/pervognsen/218ea17743e1442e59bb60d29b1aa725 ?
    
    Vladimir
    
    >
    
  50. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-16T10:02:49Z

    On Fri, Jul 16, 2021 at 1:44 AM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    >
    > Have you considered shift-based DFA for a portable implementation
    https://gist.github.com/pervognsen/218ea17743e1442e59bb60d29b1aa725 ?
    
    I did consider some kind of DFA a while back and it was too slow.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  51. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-16T23:18:33Z

    My v16 experimental patches were a bit messy, so I've organized an
    experimental series that applies cumulatively, to try to trace the effects
    of various things.
    
    v17-0001 is the same as v14. 0002 is a stripped-down implementation of
    Amit's chunk idea for multibyte, and it's pretty good on x86. On Power8,
    not so much. 0003 and 0004 are shot-in-the-dark guesses to improve it on
    Power8, with some success, but end up making x86 weirdly slow, so I'm
    afraid that could happen on other platforms as well.
    
    v14 still looks like the safe bet for now. It also has the advantage of
    using the same function both in and out of the fastpath, which will come in
    handy when moving it to src/port as the fallback for SSE.
    
    Power8, gcc 4.8:
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        2944 |  1523 |   871 |    1473 |   1509
    
    v17-0001:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         888 |   607 |   179 |     777 |   1328
    
    v17-0002:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1017 |   718 |   156 |    1213 |   2138
    
    v17-0003:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1205 |   662 |   180 |     767 |   1256
    
    v17-0004:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1085 |   660 |   224 |     868 |   1369
    
    
    Macbook x86, clang 12:
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         974 |   691 |   370 |     456 |    526
    
    v17-0001:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         674 |   346 |    78 |     309 |    504
    
    v17-0002:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         516 |   324 |    78 |     331 |    544
    
    v17-0003:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         621 |   537 |   323 |     413 |    602
    
    v17-0004:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         576 |   439 |   154 |     557 |    915
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  52. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-17T00:02:33Z

    Forgot the attachments...
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  53. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-19T01:26:47Z

    I wrote:
    
    > On Fri, Jul 16, 2021 at 1:44 AM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    > >
    > > Have you considered shift-based DFA for a portable implementation
    https://gist.github.com/pervognsen/218ea17743e1442e59bb60d29b1aa725 ?
    >
    > I did consider some kind of DFA a while back and it was too slow.
    
    I took a closer look at this "shift-based DFA", and it seemed pretty
    straightforward to implement this on top of my DFA attempt from some months
    ago. The DFA technique is not a great fit with our API, since we need to
    return how many bytes we found valid. On x86 (not our target for the
    fallback, but convenient to test) all my attempts were either worse than
    HEAD in multiple cases, or showed no improvement for the important ASCII
    case. On Power8, it's more compelling, and competitive with v14, so I'll
    characterize it on that platform as I describe the patch series:
    
    0001 is a pure DFA, and has decent performance on multibyte, but terrible
    on ascii.
    0002 dispatches on the leading byte category, unrolls the DFA loop
    according to how many valid bytes we need, and only checks the DFA state
    afterwards. It's good on multibyte (3-byte, at least) but still terrible on
    ascii.
    0003 adds a 1-byte ascii fast path -- while robust on all inputs, it still
    regresses a bit on ascii.
    0004 uses the same 8-byte ascii check as previous patches do.
    0005 and 0006 use combinations of 1- and 8-byte ascii checks similar to in
    v17.
    
    0005 seems the best on Power8, and is very close to v4. FWIW, v14's
    measurements seem lucky and fragile -- if I change any little thing, even
    
    - return -1;
    + return 0;
    
    it easily loses 100-200ms on non-pure-ascii tests. That said, v14 still
    seems the logical choice, unless there is some further tweak on top of v17
    or v18 that gives some non-x86 platform a significant boost.
    
    Power8, gcc 4.8:
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        2944 |  1523 |   871 |    1473 |   1509
    
    v18-0001:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1257 |  1681 |  1385 |    1744 |   2018
    
    v18-0002:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         951 |  1381 |  1217 |    1469 |   1172
    
    v18-0003:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         911 |  1111 |   942 |    1112 |    865
    
    v18-0004:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         987 |   730 |   222 |    1325 |   2306
    
    v18-0005:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         962 |   664 |   180 |     928 |   1179
    
    v18-0006:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         908 |   663 |   244 |    1026 |   1464
    
    and for comparison,
    
    v14:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         888 |   607 |   179 |     777 |   1328
    
    v17-0003:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1205 |   662 |   180 |     767 |   1256
    
    
    Macbook, clang 12:
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         974 |   691 |   370 |     456 |    526
    
    v18-0001:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1334 |  2713 |  2802 |    2665 |   2541
    
    v18-0002:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         733 |  1212 |  1064 |    1034 |   1007
    
    v18-0003:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         653 |   560 |   370 |     420 |    465
    
    v18-0004:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         574 |   402 |    88 |     584 |   1033
    
    v18-0005:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1345 |   730 |   334 |     578 |    909
    
    v18-0006:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         674 |   485 |   153 |     594 |    989
    
    and for comparison,
    
    v14:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         674 |   346 |    78 |     309 |    504
    
    v17-0002:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         516 |   324 |    78 |     331 |    544
    
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com
    
  54. Re: speed up verifying UTF-8

    Amit Khandekar <amitdkhan.pg@gmail.com> — 2021-07-19T05:23:22Z

    On Sat, 17 Jul 2021 at 04:48, John Naylor <john.naylor@enterprisedb.com> wrote:
    > v17-0001 is the same as v14. 0002 is a stripped-down implementation of Amit's
    > chunk idea for multibyte, and it's pretty good on x86. On Power8, not so
    > much. 0003 and 0004 are shot-in-the-dark guesses to improve it on Power8,
    > with some success, but end up making x86 weirdly slow, so I'm afraid that
    > could happen on other platforms as well.
    
    Thanks for trying the chunk approach. I tested your v17 versions on
    Arm64. For the chinese characters, v17-0002 gave some improvement over
    v14. But for all the other character sets, there was around 10%
    degradation w.r.t. v14. I thought maybe the hhton64 call and memcpy()
    for each mb character might be the culprit, so I tried iterating over
    all the characters in the chunk within the same pg_utf8_verify_one()
    function by left-shifting the bits. But that worsened the figures. So
    I gave up that idea.
    
    Here are the numbers on Arm64 :
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1781 |  1095 |   628 |     944 |   1151
    
    v14:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         852 |   484 |   144 |     584 |    971
    
    
    v17-0001+2:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         731 |   520 |   152 |     645 |   1118
    
    
    Haven't looked at your v18 patch set yet.
    
    
    
    
  55. Re: speed up verifying UTF-8

    Vladimir Sitnikov <sitnikov.vladimir@gmail.com> — 2021-07-19T13:42:57Z

    Thank you,
    
    It looks like it is important to have shrx for x86 which appears only when
    -march=x86-64-v3 is used (see
    https://github.com/golang/go/issues/47120#issuecomment-877629712 ).
    Just in case: I know x86 wound not use fallback implementation, however,
    the sole purpose of shift-based DFA is to fold all the data-dependent ops
    into a single instruction.
    
    An alternative idea: should we optimize for validation of **valid** inputs
    rather than optimizing the worst case?
    In other words, what if the implementation processes all characters always
    and uses a slower method in case of validation failure?
    I would guess it is more important to be faster with accepting valid input
    rather than "faster to reject invalid input".
    
    In shift-DFA approach, it would mean the validation loop would be simpler
    with fewer branches (see https://godbolt.org/z/hhMxhT6cf ):
    
    static inline int
    pg_is_valid_utf8(const unsigned char *s, const unsigned char *end) {
        uint64 class;
        uint64 state = BGN;
        while (s < end) { // clang unrolls the loop
            class = ByteCategory[*s++];
            state = class >> (state & DFA_MASK); // <-- note that AND is fused
    into the shift operation
        }
        return (state & DFA_MASK) != ERR;
    }
    
    Note: GCC does not seem to unroll "while(s<end)" loop by default, so manual
    unroll might be worth trying:
    
    static inline int
    pg_is_valid_utf8(const unsigned char *s, const unsigned char *end) {
        uint64 class;
        uint64 state = BGN;
        while(s < end + 4) {
            for(int i = 0; i < 4; i++) {
                class = ByteCategory[*s++];
                state = class >> (state & DFA_MASK);
            }
        }
        while(s < end) {
            class = ByteCategory[*s++];
            state = class >> (state & DFA_MASK);
        }
        return (state & DFA_MASK) != ERR;
    }
    
    ----
    
    static int pg_utf8_verifystr2(const unsigned char *s, int len) {
        if (pg_is_valid_utf8(s, s+len)) { // fast path: if string is valid,
    then just accept it
            return s + len;
        }
        // slow path: the string is not valid, perform a slower analysis
        return s + ....;
    }
    
    Vladimir
    
  56. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-19T15:07:15Z

    On Mon, Jul 19, 2021 at 9:43 AM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    
    > It looks like it is important to have shrx for x86 which appears only
    when -march=x86-64-v3 is used (see
    https://github.com/golang/go/issues/47120#issuecomment-877629712 ).
    > Just in case: I know x86 wound not use fallback implementation, however,
    the sole purpose of shift-based DFA is to fold all the data-dependent ops
    into a single instruction.
    
    I saw mention of that instruction, but didn't understand how important it
    was, thanks.
    
    > An alternative idea: should we optimize for validation of **valid**
    inputs rather than optimizing the worst case?
    > In other words, what if the implementation processes all characters
    always and uses a slower method in case of validation failure?
    > I would guess it is more important to be faster with accepting valid
    input rather than "faster to reject invalid input".
    
    > static int pg_utf8_verifystr2(const unsigned char *s, int len) {
    >     if (pg_is_valid_utf8(s, s+len)) { // fast path: if string is valid,
    then just accept it
    >         return s + len;
    >     }
    >     // slow path: the string is not valid, perform a slower analysis
    >     return s + ....;
    > }
    
    That might be workable. We have to be careful because in COPY FROM,
    validation is performed on 64kB chunks, and the boundary could fall in the
    middle of a multibyte sequence. In the SSE version, there is this comment:
    
    + /*
    + * NB: This check must be strictly greater-than, otherwise an invalid byte
    + * at the end might not get detected.
    + */
    + while (len > sizeof(__m128i))
    
    ...which should have more detail on this.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  57. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-20T21:24:33Z

    > On Mon, Jul 19, 2021 at 9:43 AM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    
    > > An alternative idea: should we optimize for validation of **valid**
    inputs rather than optimizing the worst case?
    > > In other words, what if the implementation processes all characters
    always and uses a slower method in case of validation failure?
    > > I would guess it is more important to be faster with accepting valid
    input rather than "faster to reject invalid input".
    >
    > > static int pg_utf8_verifystr2(const unsigned char *s, int len) {
    > >     if (pg_is_valid_utf8(s, s+len)) { // fast path: if string is valid,
    then just accept it
    > >         return s + len;
    > >     }
    > >     // slow path: the string is not valid, perform a slower analysis
    > >     return s + ....;
    > > }
    
    This turned out to be a really good idea (v19 attached):
    
    Power8, gcc 4.8:
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        2944 |  1523 |   871 |    1473 |   1509
    
    v14:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         888 |   607 |   179 |     777 |   1328
    
    v19:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         809 |   472 |   223 |     558 |    805
    
    x86 Macbook, clang 12:
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         974 |   691 |   370 |     456 |    526
    
    v14:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         674 |   346 |    78 |     309 |    504
    
    v19:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         379 |   181 |    94 |     219 |    376
    
    Note that the branchy code's worst case (mixed8) is here the same speed as
    multibyte. With Vladimir's idea * , we call check_ascii only every 8 bytes
    of input, not every time we verify one multibyte character. Also, we only
    have to check the DFA state every time we loop over 8 bytes, not every time
    we step through the DFA. That means we have to walk backwards at the end to
    find the last leading byte, but the SSE code already knew how to do that,
    so I used that logic here in the caller, which will allow some
    simplification of how the SSE code returns.
    
    The state check is likely why the ascii case is slightly slower than v14.
    We could go back to checking ascii 16 bytes at a time, since there's little
    penalty for doing so.
    
    * (Greg was thinking the same thing upthread, but I don't think the branchy
    code I posted at the time could have taken advantage of this)
    
    I'm pretty confident this improvement is architecture-independent. Next
    month I'll clean this up and rebase the SSE patch over this.
    
    I wrote:
    
    > + /*
    > + * NB: This check must be strictly greater-than, otherwise an invalid
    byte
    > + * at the end might not get detected.
    > + */
    > + while (len > sizeof(__m128i))
    
    Note to self: I actually think this isn't needed anymore since I changed
    how the SSE code deals with remainder sequences at the end.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  58. Re: [POC] verifying UTF-8 using SIMD instructions

    Thomas Munro <thomas.munro@gmail.com> — 2021-07-21T15:29:21Z

    On Sat, Mar 13, 2021 at 4:37 AM John Naylor
    <john.naylor@enterprisedb.com> wrote:
    > On Fri, Mar 12, 2021 at 9:14 AM Amit Khandekar <amitdkhan.pg@gmail.com> wrote:
    > > I was not thinking about auto-vectorizing the code in
    > > pg_validate_utf8_sse42(). Rather, I was considering auto-vectorization
    > > inside the individual helper functions that you wrote, such as
    > > _mm_setr_epi8(), shift_right(), bitwise_and(), prev1(), splat(),
    >
    > If the PhD holders who came up with this algorithm thought it possible to do it that way, I'm sure they would have. In reality, simdjson has different files for SSE4, AVX, AVX512, NEON, and Altivec. We can incorporate any of those as needed. That's a PG15 project, though, and I'm not volunteering.
    
    Just for fun/experimentation, here's a quick (and probably too naive)
    translation of those helper functions to NEON, on top of the v15
    patch.
    
  59. Re: speed up verifying UTF-8

    Vladimir Sitnikov <sitnikov.vladimir@gmail.com> — 2021-07-21T16:13:07Z

    >I'm pretty confident this improvement is architecture-independent.
    
    Thanks for testing it with different architectures.
    
    It looks like the same utf8_advance function is good for both fast-path and
    for the slow path.
    Then pg_utf8_verifychar could be removed altogether along with the
    corresponding IS_*_BYTE_LEAD macros.
    
    Vladimir
    
  60. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-21T16:41:53Z

    On Wed, Jul 21, 2021 at 12:13 PM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    > It looks like the same utf8_advance function is good for both fast-path
    and for the slow path.
    > Then pg_utf8_verifychar could be removed altogether along with the
    corresponding IS_*_BYTE_LEAD macros.
    
    pg_utf8_verifychar() is a public function usually called
    through pg_wchar_table[], so it needs to remain in any case.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  61. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-21T18:16:38Z

    On Wed, Jul 21, 2021 at 11:29 AM Thomas Munro <thomas.munro@gmail.com>
    wrote:
    
    > Just for fun/experimentation, here's a quick (and probably too naive)
    > translation of those helper functions to NEON, on top of the v15
    > patch.
    
    Neat! It's good to make it more architecture-agnostic, and I'm sure we can
    use quite a bit of this. I don't know enough about NEON to comment
    intelligently, but a quick glance through the simdjson source show a couple
    differences that might be worth a look:
    
     to_bool(const pg_u8x16_t v)
     {
    +#if defined(USE_NEON)
    + return vmaxvq_u32((uint32x4_t) v) != 0;
    
    --> return vmaxvq_u8(*this) != 0;
    
     vzero()
     {
    +#if defined(USE_NEON)
    + return vmovq_n_u8(0);
    
    --> return vdupq_n_u8(0); // or equivalently, splat(0)
    
    is_highbit_set(const pg_u8x16_t v)
     {
    +#if defined(USE_NEON)
    + return to_bool(bitwise_and(v, vmovq_n_u8(0x80)));
    
    --> return vmaxq_u8(v) > 0x7F
    
    (Technically, their convention is: is_ascii(v) { return vmaxq_u8(v) < 0x80;
    } , but same effect)
    
    +#if defined(USE_NEON)
    +static pg_attribute_always_inline pg_u8x16_t
    +vset(uint8 v0, uint8 v1, uint8 v2, uint8 v3,
    + uint8 v4, uint8 v5, uint8 v6, uint8 v7,
    + uint8 v8, uint8 v9, uint8 v10, uint8 v11,
    + uint8 v12, uint8 v13, uint8 v14, uint8 v15)
    +{
    + uint8 pg_attribute_aligned(16) values[16] = {
    + v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15
    + };
    + return vld1q_u8(values);
    +}
    
    --> They have this strange beast instead:
    
      // Doing a load like so end ups generating worse code.
      // uint8_t array[16] = {x1, x2, x3, x4, x5, x6, x7, x8,
      //                     x9, x10,x11,x12,x13,x14,x15,x16};
      // return vld1q_u8(array);
      uint8x16_t x{};
      // incredibly, Visual Studio does not allow x[0] = x1
      x = vsetq_lane_u8(x1, x, 0);
      x = vsetq_lane_u8(x2, x, 1);
      x = vsetq_lane_u8(x3, x, 2);
    ...
      x = vsetq_lane_u8(x15, x, 14);
      x = vsetq_lane_u8(x16, x, 15);
      return x;
    
    Since you aligned the array, that might not have the problem alluded to
    above, and it looks nicer.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  62. Re: [POC] verifying UTF-8 using SIMD instructions

    Thomas Munro <thomas.munro@gmail.com> — 2021-07-22T00:07:26Z

    On Thu, Jul 22, 2021 at 6:16 AM John Naylor
    <john.naylor@enterprisedb.com> wrote:
    > Neat! It's good to make it more architecture-agnostic, and I'm sure we can use quite a bit of this.
    
    One question is whether this "one size fits all" approach will be
    extensible to wider SIMD.
    
    >  to_bool(const pg_u8x16_t v)
    >  {
    > +#if defined(USE_NEON)
    > + return vmaxvq_u32((uint32x4_t) v) != 0;
    >
    > --> return vmaxvq_u8(*this) != 0;
    
    I chose that lane width because I saw an unsubstantiated claim
    somewhere that it might be faster, but I have no idea if it matters.
    The u8 code looks more natural anyway.  Changed.
    
    >  vzero()
    >  {
    > +#if defined(USE_NEON)
    > + return vmovq_n_u8(0);
    >
    > --> return vdupq_n_u8(0); // or equivalently, splat(0)
    
    I guess it doesn't make a difference which builtin you use here, but I
    was influenced by the ARM manual which says the vdupq form is
    generated for immediate values.
    
    > is_highbit_set(const pg_u8x16_t v)
    >  {
    > +#if defined(USE_NEON)
    > + return to_bool(bitwise_and(v, vmovq_n_u8(0x80)));
    >
    > --> return vmaxq_u8(v) > 0x7F
    
    Ah, of course.  Much nicer!
    
    > +#if defined(USE_NEON)
    > +static pg_attribute_always_inline pg_u8x16_t
    > +vset(uint8 v0, uint8 v1, uint8 v2, uint8 v3,
    > + uint8 v4, uint8 v5, uint8 v6, uint8 v7,
    > + uint8 v8, uint8 v9, uint8 v10, uint8 v11,
    > + uint8 v12, uint8 v13, uint8 v14, uint8 v15)
    > +{
    > + uint8 pg_attribute_aligned(16) values[16] = {
    > + v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15
    > + };
    > + return vld1q_u8(values);
    > +}
    >
    > --> They have this strange beast instead:
    >
    >   // Doing a load like so end ups generating worse code.
    >   // uint8_t array[16] = {x1, x2, x3, x4, x5, x6, x7, x8,
    >   //                     x9, x10,x11,x12,x13,x14,x15,x16};
    >   // return vld1q_u8(array);
    >   uint8x16_t x{};
    >   // incredibly, Visual Studio does not allow x[0] = x1
    >   x = vsetq_lane_u8(x1, x, 0);
    >   x = vsetq_lane_u8(x2, x, 1);
    >   x = vsetq_lane_u8(x3, x, 2);
    > ...
    >   x = vsetq_lane_u8(x15, x, 14);
    >   x = vsetq_lane_u8(x16, x, 15);
    >   return x;
    >
    > Since you aligned the array, that might not have the problem alluded to above, and it looks nicer.
    
    Strange indeed.  We should probably poke around in the assember and
    see... it might be that MSVC doesn't like it, and I was just
    cargo-culting the alignment.  I don't expect the generated code to
    really "load" anything of course, it should ideally be some kind of
    immediate mov...
    
    FWIW here are some performance results from my humble RPI4:
    
    master:
    
     chinese | mixed | ascii
    ---------+-------+-------
        4172 |  2763 |  1823
    (1 row)
    
    Your v15 patch:
    
     chinese | mixed | ascii
    ---------+-------+-------
        2267 |  1248 |   399
    (1 row)
    
    Your v15 patch set + the NEON patch, configured with USE_UTF8_SIMD=1:
    
     chinese | mixed | ascii
    ---------+-------+-------
         909 |   620 |   318
    (1 row)
    
    It's so good I wonder if it's producing incorrect results :-)
    
    I also tried to do a quick and dirty AltiVec patch to see if it could
    fit into the same code "shape", with less immediate success: it works
    out slower than the fallback code on the POWER7 machine I scrounged an
    account on.  I'm not sure what's wrong there, but maybe it's a uesful
    start (I'm probably confused about endianness, or the encoding of
    boolean vectors which may be different (is true 0x01or 0xff, does it
    matter?), or something else, and it's falling back on errors all the
    time?).
    
  63. Re: [POC] verifying UTF-8 using SIMD instructions

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-22T11:38:50Z

    On Wed, Jul 21, 2021 at 8:08 PM Thomas Munro <thomas.munro@gmail.com> wrote:
    >
    > On Thu, Jul 22, 2021 at 6:16 AM John Naylor
    
    > One question is whether this "one size fits all" approach will be
    > extensible to wider SIMD.
    
    Sure, it'll just take a little more work and complexity. For one, 16-byte
    SIMD can operate on 32-byte chunks with a bit of repetition:
    
    -       __m128i         input;
    +       __m128i         input1;
    +       __m128i         input2;
    
    -#define SIMD_STRIDE_LENGTH (sizeof(__m128i))
    +#define SIMD_STRIDE_LENGTH 32
    
            while (len >= SIMD_STRIDE_LENGTH)
            {
    -               input = vload(s);
    +               input1 = vload(s);
    +               input2 = vload(s + sizeof(input1));
    
    -               check_for_zeros(input, &error);
    +               check_for_zeros(input1, &error);
    +               check_for_zeros(input2, &error);
    
                    /*
                     * If the chunk is all ASCII, we can skip the full UTF-8
    check, but we
    @@ -460,17 +463,18 @@ pg_validate_utf8_sse42(const unsigned char *s, int
    len)
                     * sequences at the end. We only update prev_incomplete if
    the chunk
                     * contains non-ASCII, since the error is cumulative.
                     */
    -               if (is_highbit_set(input))
    +               if (is_highbit_set(bitwise_or(input1, input2)))
                    {
    -                       check_utf8_bytes(prev, input, &error);
    -                       prev_incomplete = is_incomplete(input);
    +                       check_utf8_bytes(prev, input1, &error);
    +                       check_utf8_bytes(input1, input2, &error);
    +                       prev_incomplete = is_incomplete(input2);
                    }
                    else
                    {
                            error = bitwise_or(error, prev_incomplete);
                    }
    
    -               prev = input;
    +               prev = input2;
                    s += SIMD_STRIDE_LENGTH;
                    len -= SIMD_STRIDE_LENGTH;
            }
    
    So with a few #ifdefs, we can accommodate two sizes if we like.
    
    For another, the prevN() functions would need to change, at least on x86 --
    that would require replacing _mm_alignr_epi8() with _mm256_alignr_epi8()
    plus _mm256_permute2x128_si256(). Also, we might have to do something with
    the vector typedef.
    
    That said, I think we can punt on that until we have an application that's
    much more compute-intensive. As it is with SSE4, COPY FROM WHERE <selective
    predicate> already pushes the utf8 validation way down in profiles.
    
    > FWIW here are some performance results from my humble RPI4:
    >
    > master:
    >
    >  chinese | mixed | ascii
    > ---------+-------+-------
    >     4172 |  2763 |  1823
    > (1 row)
    >
    > Your v15 patch:
    >
    >  chinese | mixed | ascii
    > ---------+-------+-------
    >     2267 |  1248 |   399
    > (1 row)
    >
    > Your v15 patch set + the NEON patch, configured with USE_UTF8_SIMD=1:
    >
    >  chinese | mixed | ascii
    > ---------+-------+-------
    >      909 |   620 |   318
    > (1 row)
    >
    > It's so good I wonder if it's producing incorrect results :-)
    
    Nice! If it passes regression tests, it *should* be fine, but stress
    testing would be welcome on any platform.
    
    > I also tried to do a quick and dirty AltiVec patch to see if it could
    > fit into the same code "shape", with less immediate success: it works
    > out slower than the fallback code on the POWER7 machine I scrounged an
    > account on.  I'm not sure what's wrong there, but maybe it's a uesful
    > start (I'm probably confused about endianness, or the encoding of
    > boolean vectors which may be different (is true 0x01or 0xff, does it
    > matter?), or something else, and it's falling back on errors all the
    > time?).
    
    Hmm, I have access to a power8 machine to play with, but I also don't mind
    having some type of server-class hardware that relies on the recent nifty
    DFA fallback, which performs even better on powerpc64le than v15.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  64. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-26T11:09:00Z

    Attached is v20, which has a number of improvements:
    
    1. Cleaned up and explained DFA coding.
    2. Adjusted check_ascii to return bool (now called is_valid_ascii) and to
    produce an optimized loop, using branch-free accumulators. That way, it
    doesn't need to be rewritten for different input lengths. I also think it's
    a bit easier to understand this way.
    3. Put SSE helper functions in their own file.
    4. Mostly-cosmetic edits to the configure detection.
    5. Draft commit message.
    
    With #2 above in place, I wanted to try different strides for the DFA, so
    more measurements (hopefully not much more of these):
    
    Power8, gcc 4.8
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        2944 |  1523 |   871 |    1473 |   1509
    
    v20, 8-byte stride:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1189 |   550 |   246 |     600 |    936
    
    v20, 16-byte stride (in the actual patch):
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         981 |   440 |   134 |     791 |    820
    
    v20, 32-byte stride:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         857 |   481 |   141 |     834 |    839
    
    Based on the above, I decided that 16 bytes had the best overall balance.
    Other platforms may differ, but I don't expect it to make a huge amount of
    difference.
    
    Just for fun, I was also a bit curious about what Vladimir mentioned
    upthread about x86-64-v3 offering a different shift instruction. Somehow,
    clang 12 refused to build with that target, even though the release notes
    say it can, but gcc 11 was fine:
    
    x86 Macbook, gcc 11, USE_FALLBACK_UTF8=1:
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
        1200 |   728 |   370 |     544 |    637
    
    v20:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         459 |   243 |    77 |     424 |    440
    
    v20, CFLAGS="-march=x86-64-v3 -O2" :
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         390 |   215 |    77 |     303 |    323
    
    And, gcc does generate the desired shift here:
    
    objdump -S src/port/pg_utf8_fallback.o | grep shrx
          53: c4 e2 eb f7 d1               shrxq %rdx, %rcx, %rdx
    
    While it looks good, clang can do about as good by simply unrolling all 16
    shifts in the loop, which gcc won't do. To be clear, it's irrelevant, since
    x86-64-v3 includes AVX2, and if we had that we would just use it with the
    SIMD algorithm.
    
    Macbook x86, clang 12:
    
    HEAD:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         974 |   691 |   370 |     456 |    526
    
    v20, USE_FALLBACK_UTF8=1:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         351 |   172 |    88 |     349 |    350
    
    v20, with SSE4:
     chinese | mixed | ascii | mixed16 | mixed8
    ---------+-------+-------+---------+--------
         142 |    92 |    59 |     141 |    141
    
    I'm pretty happy with the patch at this point.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  65. Re: speed up verifying UTF-8

    Vladimir Sitnikov <sitnikov.vladimir@gmail.com> — 2021-07-26T11:55:29Z

    Just wondering, do you have the code in a GitHub/Gitlab branch?
    
    >+ utf8_advance(s, state, len);
    >+
    >+ /*
    >+ * If we saw an error during the loop, let the caller handle it. We treat
    >+ * all other states as success.
    >+ */
    >+ if (state == ERR)
    >+ return 0;
    
    Did you mean state = utf8_advance(s, state, len); there? (reassign state
    variable)
    
    >I wanted to try different strides for the DFA
    
    Does that (and "len >= 32" condition) mean the patch does not improve
    validation of the shorter strings (the ones less than 32 bytes)?
    It would probably be nice to cover them as well (e.g. with 4 or 8-byte
    strides)
    
    Vladimir
    
  66. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-26T12:56:52Z

    On Mon, Jul 26, 2021 at 7:55 AM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    >
    > Just wondering, do you have the code in a GitHub/Gitlab branch?
    >
    > >+ utf8_advance(s, state, len);
    > >+
    > >+ /*
    > >+ * If we saw an error during the loop, let the caller handle it. We
    treat
    > >+ * all other states as success.
    > >+ */
    > >+ if (state == ERR)
    > >+ return 0;
    >
    > Did you mean state = utf8_advance(s, state, len); there? (reassign state
    variable)
    
    Yep, that's a bug, thanks for catching!
    
    > >I wanted to try different strides for the DFA
    >
    > Does that (and "len >= 32" condition) mean the patch does not improve
    validation of the shorter strings (the ones less than 32 bytes)?
    
    Right. Also, the 32 byte threshold was just a temporary need for testing
    32-byte stride -- testing different thresholds wouldn't hurt.  I'm not
    terribly concerned about short strings, though, as long as we don't
    regress.  That said, Heikki had something in his v14 [1] that we could use:
    
    +/*
    + * Subroutine of pg_utf8_verifystr() to check on char. Returns the length
    of the
    + * character at *s in bytes, or 0 on invalid input or premature end of
    input.
    + *
    + * XXX: could this be combined with pg_utf8_verifychar above?
    + */
    +static inline int
    +pg_utf8_verify_one(const unsigned char *s, int len)
    
    It would be easy to replace pg_utf8_verifychar with this. It might even
    speed up the SQL function length_in_encoding() -- that would be a better
    reason to do it.
    
    [1]
    https://www.postgresql.org/message-id/2f95e70d-4623-87d4-9f24-ca534155f179%40iki.fi
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  67. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-26T12:58:37Z

    On Mon, Jul 26, 2021 at 7:55 AM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    >
    > Just wondering, do you have the code in a GitHub/Gitlab branch?
    
    Sorry, I didn't see this earlier. No, I don't.
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  68. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-28T18:12:11Z

    I wrote:
    
    > On Mon, Jul 26, 2021 at 7:55 AM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    > >
    > > >+ utf8_advance(s, state, len);
    > > >+
    > > >+ /*
    > > >+ * If we saw an error during the loop, let the caller handle it. We
    treat
    > > >+ * all other states as success.
    > > >+ */
    > > >+ if (state == ERR)
    > > >+ return 0;
    > >
    > > Did you mean state = utf8_advance(s, state, len); there? (reassign
    state variable)
    >
    > Yep, that's a bug, thanks for catching!
    
    Fixed in v21, with a regression test added. Also, utf8_advance() now
    directly changes state by a passed pointer rather than returning a value.
    Some cosmetic changes:
    
    s/valid_bytes/non_error_bytes/ since the former is kind of misleading now.
    
    Some other var name and symbol changes. In my first DFA experiment, ASC
    conflicted with the parser or scanner somehow, but it doesn't here, so it's
    clearer to use this.
    
    Rewrote a lot of comments about the state machine and regression tests.
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  69. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-07-30T01:12:33Z

    On Mon, Jul 26, 2021 at 8:56 AM John Naylor <john.naylor@enterprisedb.com>
    wrote:
    >
    > >
    > > Does that (and "len >= 32" condition) mean the patch does not improve
    validation of the shorter strings (the ones less than 32 bytes)?
    >
    > Right. Also, the 32 byte threshold was just a temporary need for testing
    32-byte stride -- testing different thresholds wouldn't hurt.  I'm not
    terribly concerned about short strings, though, as long as we don't
    regress.
    
    I put together the attached quick test to try to rationalize the fast-path
    threshold. (In case it isn't obvious, it must be at least 16 on all builds,
    since wchar.c doesn't know which implementation it's calling, and SSE
    register width sets the lower bound.) I changed the threshold first to 16,
    and then 100000, which will force using the byte-at-a-time code.
    
    If we have only 16 bytes in the input, it still seems to be faster to use
    SSE, even though it's called through a function pointer on x86. I didn't
    test the DFA path, but I don't think the conclusion would be different.
    I'll include the 16 threshold next time I need to update the patch.
    
    Macbook x86, clang 12:
    
    master + use 16:
     asc16 | asc32 | asc64 | mb16 | mb32 | mb64
    -------+-------+-------+------+------+------
       270 |   279 |   282 |  291 |  296 |  304
    
    force byte-at-a-time:
     asc16 | asc32 | asc64 | mb16 | mb32 | mb64
    -------+-------+-------+------+------+------
       277 |   292 |   310 |  296 |  317 |  362
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  70. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-08-04T11:22:57Z

    I wrote:
    > If we have only 16 bytes in the input, it still seems to be faster to use
    SSE, even though it's called through a function pointer on x86. I didn't
    test the DFA path, but I don't think the conclusion would be different.
    I'll include the 16 threshold next time I need to update the patch.
    
    v22 attached, which changes the threshold to 16, with a few other cosmetic
    adjustments, mostly in the comments.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  71. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-08-24T16:00:28Z

    Naively, the shift-based DFA requires 64-bit integers to encode the
    transitions, but I recently came across an idea from Dougall Johnson of
    using the Z3 SMT solver to pack the transitions into 32-bit integers [1].
    That halves the size of the transition table for free. I adapted that
    effort to the existing conventions in v22 and arrived at the attached
    python script. Running the script outputs the following:
    
    $ python dfa-pack-pg.py
    offsets: [0, 11, 16, 1, 5, 6, 20, 25, 30]
    transitions:
    00000000000000000000000000000000 0x0
    00000000000000000101100000000000 0x5800
    00000000000000001000000000000000 0x8000
    00000000000000000000100000000000 0x800
    00000000000000000010100000000000 0x2800
    00000000000000000011000000000000 0x3000
    00000000000000001010000000000000 0xa000
    00000000000000001100100000000000 0xc800
    00000000000000001111000000000000 0xf000
    01000001000010110000000000100000 0x410b0020
    00000011000010110000000000100000 0x30b0020
    00000010000010110000010000100000 0x20b0420
    
    I'll include something like the attached text file diff in the next patch.
    Some comments are now outdated, but this is good enough for demonstration.
    
    [1] https://gist.github.com/dougallj/166e326de6ad4cf2c94be97a204c025f
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  72. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-08-26T15:35:54Z

    I wrote:
    
    > Naively, the shift-based DFA requires 64-bit integers to encode the
    transitions, but I recently came across an idea from Dougall Johnson of
    using the Z3 SMT solver to pack the transitions into 32-bit integers [1].
    That halves the size of the transition table for free. I adapted that
    effort to the existing conventions in v22 and arrived at the attached
    python script.
    > [...]
    > I'll include something like the attached text file diff in the next
    patch. Some comments are now outdated, but this is good enough for
    demonstration.
    
    Attached is v23 incorporating the 32-bit transition table, with the
    necessary comment adjustments.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  73. Re: speed up verifying UTF-8

    Vladimir Sitnikov <sitnikov.vladimir@gmail.com> — 2021-08-26T16:08:54Z

    >Attached is v23 incorporating the 32-bit transition table, with the
    necessary comment adjustments
    
    32bit table is nice.
    
    
    Would you please replace
    https://github.com/BobSteagall/utf_utils/blob/master/src/utf_utils.cpp URL
    with
    https://github.com/BobSteagall/utf_utils/blob/6b7a465265de2f5fa6133d653df0c9bdd73bbcf8/src/utf_utils.cpp
    in the header of src/port/pg_utf8_fallback.c?
    
    It would make the URL more stable in case the file gets renamed.
    
    Vladimir
    
  74. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-10-19T21:42:40Z

    I've decided I'm not quite comfortable with the additional complexity in
    the build system introduced by the SIMD portion of the previous patches. It
    would make more sense if the pure C portion were unchanged, but with the
    shift-based DFA plus the bitwise ASCII check, we have a portable
    implementation that's still a substantial improvement over the current
    validator. In v24, I've included only that much, and the diff is only about
    1/3 as many lines. If future improvements to COPY FROM put additional
    pressure on this path, we can always add SIMD support later.
    
    One thing not in this patch is a possible improvement to
    pg_utf8_verifychar() that Heikki and I worked on upthread as part of
    earlier attempts to rewrite pg_utf8_verifystr(). That's worth looking into
    separately.
    
    On Thu, Aug 26, 2021 at 12:09 PM Vladimir Sitnikov <
    sitnikov.vladimir@gmail.com> wrote:
    >
    > >Attached is v23 incorporating the 32-bit transition table, with the
    necessary comment adjustments
    >
    > 32bit table is nice.
    
    Thanks for taking a look!
    
    > Would you please replace
    https://github.com/BobSteagall/utf_utils/blob/master/src/utf_utils.cpp URL
    with
    >
    https://github.com/BobSteagall/utf_utils/blob/6b7a465265de2f5fa6133d653df0c9bdd73bbcf8/src/utf_utils.cpp
    > in the header of src/port/pg_utf8_fallback.c?
    >
    > It would make the URL more stable in case the file gets renamed.
    >
    > Vladimir
    >
    
    Makes sense, so done that way.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  75. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-12-08T18:11:46Z

    It occurred to me that the DFA + ascii quick check approach could also
    be adapted to speed up some cases where we currently walk a string
    counting characters, like this snippet in
    text_position_get_match_pos():
    
    /* Convert the byte position to char position. */
    while (state->refpoint < state->last_match)
    {
        state->refpoint += pg_mblen(state->refpoint);
        state->refpos++;
    }
    
    This coding changed in 9556aa01c69 (Use single-byte
    Boyer-Moore-Horspool search even with multibyte encodings), in which I
    found the majority of cases were faster, but some were slower. It
    would be nice to regain the speed lost and do even better.
    
    In the case of UTF-8, we could just run it through the DFA,
    incrementing a count of the states found. The number of END states
    should be the number of characters. The ascii quick check would still
    be applicable as well. I think all that is needed is to export some
    symbols and add the counting function. That wouldn't materially affect
    the current patch for input verification, and would be separate, but
    it would be nice to get the symbol visibility right up front. I've set
    this to waiting on author while I experiment with that.
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
    
    
    
  76. Re: speed up verifying UTF-8

    Heikki Linnakangas <hlinnaka@iki.fi> — 2021-12-10T18:33:48Z

    On 20/10/2021 00:42, John Naylor wrote:
    > I've decided I'm not quite comfortable with the additional complexity in 
    > the build system introduced by the SIMD portion of the previous patches. 
    > It would make more sense if the pure C portion were unchanged, but with 
    > the shift-based DFA plus the bitwise ASCII check, we have a portable 
    > implementation that's still a substantial improvement over the current 
    > validator. In v24, I've included only that much, and the diff is only 
    > about 1/3 as many lines. If future improvements to COPY FROM put 
    > additional pressure on this path, we can always add SIMD support later.
    
    +1.
    
    I had another look at this now. Looks good, just a few minor comments below:
    
    > +/*
    > + * Verify a chunk of bytes for valid ASCII, including a zero-byte check.
    > + */
    > +static inline bool
    > +is_valid_ascii(const unsigned char *s, int len)
    > +{
    > +	uint64		chunk,
    > +				highbit_cum = UINT64CONST(0),
    > +				zero_cum = UINT64CONST(0x8080808080808080);
    > +
    > +	Assert(len % sizeof(chunk) == 0);
    > +
    > +	while (len >= sizeof(chunk))
    > +	{
    > +		memcpy(&chunk, s, sizeof(chunk));
    > +
    > +		/*
    > +		 * Capture any zero bytes in this chunk.
    > +		 *
    > +		 * First, add 0x7f to each byte. This sets the high bit in each byte,
    > +		 * unless it was a zero. We will check later that none of the bytes in
    > +		 * the chunk had the high bit set, in which case the max value each
    > +		 * byte can have after the addition is 0x7f + 0x7f = 0xfe, and we
    > +		 * don't need to worry about carrying over to the next byte.
    > +		 *
    > +		 * If any resulting high bits are zero, the corresponding high bits in
    > +		 * the zero accumulator will be cleared.
    > +		 */
    > +		zero_cum &= (chunk + UINT64CONST(0x7f7f7f7f7f7f7f7f));
    > +
    > +		/* Capture any set bits in this chunk. */
    > +		highbit_cum |= chunk;
    > +
    > +		s += sizeof(chunk);
    > +		len -= sizeof(chunk);
    > +	}
    
    This function assumes that the input len is a multiple of 8. There's an 
    assertion for that, but it would be good to also mention it in the 
    function comment. I took me a moment to realize that.
    
    Given that assumption, I wonder if "while (len >= 0)" would marginally 
    faster. Or compute "s_end = s + len" first, and check for "while (s < 
    s_end)", so that you don't need to update 'len' in the loop.
    
    Also would be good to mention what exactly the return value means. I.e 
    "returns false if the input contains any bytes with the high-bit set, or 
    zeros".
    
    > +	/*
    > +	 * Check if any high bits in the zero accumulator got cleared.
    > +	 *
    > +	 * XXX: As noted above, the zero check is only valid if the chunk had no
    > +	 * high bits set. However, the compiler may perform these two checks in
    > +	 * any order. That's okay because if any high bits were set, we would
    > +	 * return false regardless, so invalid results from the zero check don't
    > +	 * matter.
    > +	 */
    > +	if (zero_cum != UINT64CONST(0x8080808080808080))
    > +		return false;
    
    I don't understand the "the compiler may perform these checks in any 
    order" comment. We trust the compiler to do the right thing, and only 
    reorder things when it's safe to do so. What is special here, why is it 
    worth mentioning here?
    
    > @@ -1721,7 +1777,7 @@ pg_gb18030_verifystr(const unsigned char *s, int len)
    >  	return s - start;
    >  }
    >  
    > -static int
    > +static pg_noinline int
    >  pg_utf8_verifychar(const unsigned char *s, int len)
    >  {
    >  	int			l;
    
    Why force it to not be inlined?
    
    > + * In a shift-based DFA, the input byte is an index into array of integers
    > + * whose bit pattern encodes the state transitions. To compute the current
    > + * state, we simply right-shift the integer by the current state and apply a
    > + * mask. In this scheme, the address of the transition only depends on the
    > + * input byte, so there is better pipelining.
    
    Should be "To compute the *next* state, ...", I think.
    
    The way the state transition table works is pretty inscrutable. That's 
    understandable, because the values were found by an SMT solver, so I'm 
    not sure if anything can be done about it.
    
    - Heikki
    
    
    
    
  77. RE: [EXTERNAL] Re: speed up verifying UTF-8

    Godfrin, Philippe E <philippe.godfrin@nov.com> — 2021-12-10T18:49:05Z

    >-----Original Message-----
    >From: Heikki Linnakangas <hlinnaka@iki.fi> 
    >Sent: Friday, December 10, 2021 12:34 PM
    >To: John Naylor <john.naylor@enterprisedb.com>; Vladimir Sitnikov <sitnikov.vladimir@gmail.com>
    >Cc: pgsql-hackers <pgsql-hackers@postgresql.org>; Amit Khandekar <amitdkhan.pg@gmail.com>; Thomas Munro <thomas.munro@gmail.com>; Greg Stark <stark@mit.edu>
    >Subject: [EXTERNAL] Re: speed up verifying UTF-8
    >
    >On 20/10/2021 00:42, John Naylor wrote:
    >> I've decided I'm not quite comfortable with the additional complexity 
    >> in the build system introduced by the SIMD portion of the previous patches.
    >> It would make more sense if the pure C portion were unchanged, but 
    >> with the shift-based DFA plus the bitwise ASCII check, we have a 
    >> portable implementation that's still a substantial improvement over 
    >> the current validator. In v24, I've included only that much, and the 
    >> diff is only about 1/3 as many lines. If future improvements to COPY 
    >> FROM put additional pressure on this path, we can always add SIMD support later.
    >
    >+1.
    >
    >I had another look at this now. Looks good, just a few minor comments below:
    >
    >> +/*
    >> + * Verify a chunk of bytes for valid ASCII, including a zero-byte check.
    >> + */
    >> +static inline bool
    >> +is_valid_ascii(const unsigned char *s, int len) {
    >> +	uint64		chunk,
    >> +				highbit_cum = UINT64CONST(0),
    >> +				zero_cum = UINT64CONST(0x8080808080808080);
    >> +
    >> +	Assert(len % sizeof(chunk) == 0);
    >> +
    >> +	while (len >= sizeof(chunk))
    >> +	{
    >> +		memcpy(&chunk, s, sizeof(chunk));
    >> +
    >> +		/*
    >> +		 * Capture any zero bytes in this chunk.
    >> +		 *
    >> +		 * First, add 0x7f to each byte. This sets the high bit in each byte,
    >> +		 * unless it was a zero. We will check later that none of the bytes in
    >> +		 * the chunk had the high bit set, in which case the max value each
    >> +		 * byte can have after the addition is 0x7f + 0x7f = 0xfe, and we
    >> +		 * don't need to worry about carrying over to the next byte.
    >> +		 *
    >> +		 * If any resulting high bits are zero, the corresponding high bits in
    >> +		 * the zero accumulator will be cleared.
    >> +		 */
    >> +		zero_cum &= (chunk + UINT64CONST(0x7f7f7f7f7f7f7f7f));
    >> +
    >> +		/* Capture any set bits in this chunk. */
    >> +		highbit_cum |= chunk;
    >> +
    >> +		s += sizeof(chunk);
    >> +		len -= sizeof(chunk);
    >> +	}
    >
    >This function assumes that the input len is a multiple of 8. There's an assertion for that, but it would be good to also mention it in the function comment. I took me a moment to realize that.
    >
    >Given that assumption, I wonder if "while (len >= 0)" would marginally faster. Or compute "s_end = s + len" first, and check for "while (s < s_end)", so that you don't need to update 'len' in the loop.
    >
    >Also would be good to mention what exactly the return value means. I.e "returns false if the input contains any bytes with the high-bit set, or zeros".
    >
    >> +	/*
    >> +	 * Check if any high bits in the zero accumulator got cleared.
    >> +	 *
    >> +	 * XXX: As noted above, the zero check is only valid if the chunk had no
    >> +	 * high bits set. However, the compiler may perform these two checks in
    >> +	 * any order. That's okay because if any high bits were set, we would
    >> +	 * return false regardless, so invalid results from the zero check don't
    >> +	 * matter.
    >> +	 */
    >> +	if (zero_cum != UINT64CONST(0x8080808080808080))
    >> +		return false;
    >
    >I don't understand the "the compiler may perform these checks in any order" comment. We trust the compiler to do the right thing, and only reorder things when it's safe to do so. What is special here, why is it worth mentioning here?
    >
    >> @@ -1721,7 +1777,7 @@ pg_gb18030_verifystr(const unsigned char *s, int len)
    >>  	return s - start;
    >>  }
    >>  
    >> -static int
    >> +static pg_noinline int
    >>  pg_utf8_verifychar(const unsigned char *s, int len)  {
    >>  	int			l;
    >
    >Why force it to not be inlined?
    >
    >> + * In a shift-based DFA, the input byte is an index into array of 
    >> + integers
    >> + * whose bit pattern encodes the state transitions. To compute the 
    >> + current
    >> + * state, we simply right-shift the integer by the current state and 
    >> + apply a
    >> + * mask. In this scheme, the address of the transition only depends 
    >> + on the
    >> + * input byte, so there is better pipelining.
    >
    >Should be "To compute the *next* state, ...", I think.
    >
    >The way the state transition table works is pretty inscrutable. That's understandable, because the values were found by an SMT solver, so I'm not sure if anything can be done about it.
    >
    >- Heikki
    >
    
    If I remember correctly the shift instruction is very fast...
    
  78. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-12-13T15:39:37Z

    On Fri, Dec 10, 2021 at 2:33 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:
    
    > I had another look at this now. Looks good, just a few minor comments below:
    
    Thanks for reviewing! I've attached v25 to address your points.
    
    > This function assumes that the input len is a multiple of 8. There's an
    > assertion for that, but it would be good to also mention it in the
    > function comment. I took me a moment to realize that.
    
    Done.
    
    > Given that assumption, I wonder if "while (len >= 0)" would marginally
    > faster. Or compute "s_end = s + len" first, and check for "while (s <
    > s_end)", so that you don't need to update 'len' in the loop.
    
    With two chunks, gcc 4.8.5/11.2 and clang 12 will unroll the inner
    loop, so it doesn't matter:
    
    L51:
            mov     rdx, QWORD PTR [rdi]
            mov     rsi, QWORD PTR [rdi+8]
            lea     rax, [rdx+rbx]
            lea     rbp, [rsi+rbx]
            and     rax, rbp
            and     rax, r11
            cmp     rax, r11
            jne     .L66
            or      rdx, rsi
            test    rdx, r11
            jne     .L66
            sub     r8d, 16          ; refers to "len" in the caller
    pg_utf8_verifystr()
            add     rdi, 16
            cmp     r8d, 15
            jg      .L51
    
    I *think* these are the same instructions as from your version from
    some time ago that handled two integers explicitly -- I rewrote it
    like this to test different chunk sizes.
    
    (Aside on 32-byte strides: Four chunks was within the noise level of
    two chunks on the platform I tested. With 32 bytes, that increases the
    chance that a mixed input would have non-ascii and defeat this
    optimization, so should be significantly faster to make up for that.
    Along those lines, in the future we could consider SSE2 (unrolled 2 x
    16 bytes) for this path. Since it's part of the spec for x86-64, we
    wouldn't need a runtime check -- just #ifdef it inline. And we could
    piggy-back on the CRC SSE4.2 configure test for intrinsic support, so
    that would avoid adding a bunch of complexity.)
    
    That said, I think your suggestions are better on code clarity
    grounds. I'm on the fence about "while(s < s_end)", so I went with
    "while (len > 0)" because it matches the style in wchar.c.
    
    > Also would be good to mention what exactly the return value means. I.e
    > "returns false if the input contains any bytes with the high-bit set, or
    > zeros".
    
    Done.
    
    > > +     /*
    > > +      * Check if any high bits in the zero accumulator got cleared.
    > > +      *
    > > +      * XXX: As noted above, the zero check is only valid if the chunk had no
    > > +      * high bits set. However, the compiler may perform these two checks in
    > > +      * any order. That's okay because if any high bits were set, we would
    > > +      * return false regardless, so invalid results from the zero check don't
    > > +      * matter.
    > > +      */
    > > +     if (zero_cum != UINT64CONST(0x8080808080808080))
    > > +             return false;
    
    > I don't understand the "the compiler may perform these checks in any
    > order" comment. We trust the compiler to do the right thing, and only
    > reorder things when it's safe to do so. What is special here, why is it
    > worth mentioning here?
    
    Ah, that's a good question, and now that you mention it, the comment
    is silly. When looking at the assembly output a while back, I was a
    bit astonished that it didn't match my mental model of what was
    happening, so I made this note. I've removed the whole XXX comment
    here and expanded the first comment in the loop to:
    
    /*
     * Capture any zero bytes in this chunk.
     *
     * First, add 0x7f to each byte. This sets the high bit in each byte,
     * unless it was a zero. If any resulting high bits are zero, the
     * corresponding high bits in the zero accumulator will be cleared.
     *
     * If none of the bytes in the chunk had the high bit set, the max
     * value each byte can have after the addition is 0x7f + 0x7f = 0xfe,
     * and we don't need to worry about carrying over to the next byte. If
     * any input bytes did have the high bit set, it doesn't matter
     * because we check for those separately.
     */
    
    > > @@ -1721,7 +1777,7 @@ pg_gb18030_verifystr(const unsigned char *s, int len)
    > >       return s - start;
    > >  }
    > >
    > > -static int
    > > +static pg_noinline int
    > >  pg_utf8_verifychar(const unsigned char *s, int len)
    > >  {
    > >       int                     l;
    >
    > Why force it to not be inlined?
    
    Since the only direct caller is now only using it for small inputs, I
    thought about saving space, but it's not enough to matter, so I'll go
    ahead and leave it out. While at it, I removed the unnecessary
    "inline" declaration for utf8_advance(), since the compiler can do
    that anyway.
    
    > > + * In a shift-based DFA, the input byte is an index into array of integers
    > > + * whose bit pattern encodes the state transitions. To compute the current
    > > + * state, we simply right-shift the integer by the current state and apply a
    > > + * mask. In this scheme, the address of the transition only depends on the
    > > + * input byte, so there is better pipelining.
    >
    > Should be "To compute the *next* state, ...", I think.
    
    Fixed.
    
    > The way the state transition table works is pretty inscrutable. That's
    > understandable, because the values were found by an SMT solver, so I'm
    > not sure if anything can be done about it.
    
    Do you mean in general, or just the state values?
    
    Like any state machine, the code is simple, and the complexity is
    hidden in the data. Hopefully the first link I included in the comment
    is helpful.
    
    The SMT solver was only needed to allow 32-bit (instead of 64-bit)
    entries in the transition table, so it's not strictly necessary. A
    lookup table that fits in 1kB is nice from a cache perspective,
    however.
    
    With 64-bit, the state values are less weird-looking but they're still
    just arbitrary numbers. As long as ERR = 0 and the largest is at most
    9, it doesn't matter what they are, so I'm not sure it's much less
    mysterious. You can see the difference between 32-bit and 64-bit in
    [1].
    
    --
    In addition to Heikki's. review points, I've made a couple small
    additional changes from v24: I rewrote this part, so we don't need
    these macros anymore:
    
    -                       if (!IS_HIGHBIT_SET(*s) ||
    -                               IS_UTF8_2B_LEAD(*s) ||
    -                               IS_UTF8_3B_LEAD(*s) ||
    -                               IS_UTF8_4B_LEAD(*s))
    +                       if (!IS_HIGHBIT_SET(*s) || pg_utf_mblen(s) > 1)
    
    And I moved is_valid_ascii() to pg_wchar.h so it can be used
    elsewhere. I'm not sure there's a better place to put it. I tried
    using this for text_position(), for which I'll start a new thread.
    
    [1] https://www.postgresql.org/message-id/attachment/125672/v22-addendum-32-bit-transitions.txt
    
    
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
    
    --
    John Naylor
    EDB: http://www.enterprisedb.com
    
  79. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-12-17T13:29:48Z

    I plan to push v25 early next week, unless there are further comments.
    
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com
    
    
    
    
  80. Re: speed up verifying UTF-8

    John Naylor <john.naylor@enterprisedb.com> — 2021-12-20T14:24:40Z

    On Fri, Dec 17, 2021 at 9:29 AM John Naylor
    <john.naylor@enterprisedb.com> wrote:
    >
    > I plan to push v25 early next week, unless there are further comments.
    
    Pushed, thanks everyone!
    
    -- 
    John Naylor
    EDB: http://www.enterprisedb.com