[PATCH v2] libcpp: Optimize AArch64 search_line_neon

Wilco Dijkstra <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <PAWPR08MB89823B8D01B60470E17E290383A62@PAWPR08MB8982.eurprd08.prod.outlook.com>
v2: Based on Alexander's feedback, make use of the padding to get further
speedup. Improve the algorithm to use single-reg TBL lookup.

The existing AdvSIMD search_line_neon is ancient and not well optimized.
Optimize it for typical inputs (short lines) - what matters is to quickly
get the match result for the first few vectors with minimal initialization
overhead.  We simply loop until a match is found even if the input pointer
is unaligned or close to the end.  Since this may overread, it relies on
*end containing a match and CPP_BUFFER_PADDING >= 16.

Lookup the low 4 bits of each character using TBL and compare the result
with the original input.  The lookup table contains the 4 search characters
at entries MOD 16, so a match results in the same character.  This works
because the low 4 bits of the search characters are unique.

The optimized sequence is less than half that of the old code:

.L44:
        ldr     q30, [x0, 16]!
.L43:
        and     v31.16b, v30.16b, v28.16b
        tbl     v31.16b, {v29.16b}, v31.16b
        cmeq    v31.16b, v31.16b, v30.16b
        addhn   v31.8b, v31.8h, v31.8h
        fmov    x1, d31
        cbz     x1, .L44
        and     x1, x1, 1229782938247303441
        rbit    x1, x1
        clz     x1, x1
        add     x0, x0, x1, lsr 2
        ret

Performance is significantly better as a result: on a huge trace that replays
the calls from libcpp it is ~54% faster on Neoverse V2 and ~42% on Neoverse N1.

Passes bootstrap, OK for commit?

libcpp/ChangeLog:

        * lex.cc (search_line_neon): New optimized AdvSIMD implementation.

---

diff --git a/libcpp/lex.cc b/libcpp/lex.cc
index ee6d6e01803d8a98b2136e51ed92136af30f32fd..84318db97ec8168a9022a4493a050e3f09fbb61b 100644
--- a/libcpp/lex.cc
+++ b/libcpp/lex.cc
@@ -644,96 +644,55 @@ search_line_fast (const uchar *s, const uchar *end ATTRIBUTE_UNUSED)
 #elif defined (__ARM_NEON) && defined (__ARM_64BIT_STATE)
 #include "arm_neon.h"
 
-/* This doesn't have to be the exact page size, but no system may use
-   a size smaller than this.  ARMv8 requires a minimum page size of
-   4k.  The impact of being conservative here is a small number of
-   cases will take the slightly slower entry path into the main
-   loop.  */
-
-#define AARCH64_MIN_PAGE_SIZE 4096
+/* A fast AdvSIMD scanner implementation using table lookup.
+
+   Lookup the low 4 bits of each character using TBL and compare the
+   result with the original input.  The lookup table contains the 4
+   search characters at entries MOD 16, so a match results in the same
+   character.  This works because the low 4 bits of the search
+   characters are unique.
+
+   Typical statistics for number of characters till a match:
+    1-15: 30.9%
+   16-31: 22.2%
+   32-47: 18.3%
+   48-63: 14.1%
+   64-79: 13.0%
+   80-95:  1.1%
+     >96:  0.3%
+
+   To get good performance, what matters is to quickly get the match result
+   for the first few vectors with minimal initialization overhead.
+   We simply loop until a match is found even if the input pointer is
+   unaligned or close to the end.  Since this may overread, it relies on
+   *end containing a match and CPP_BUFFER_PADDING >= 16.  */
+
+static_assert (CPP_BUFFER_PADDING >= 16, "");
+
+/* TBL lookup with each search char at (ch % 16).  Avoid matching NUL by
+   setting the first entry to 1.  */
+static const uint8_t table[16] =
+  { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\n', 0, '\\', '\r', 0, '?' };
 
 static const uchar *
 search_line_neon (const uchar *s, const uchar *end ATTRIBUTE_UNUSED)
 {
-  const uint8x16_t repl_nl = vdupq_n_u8 ('\n');
-  const uint8x16_t repl_cr = vdupq_n_u8 ('\r');
-  const uint8x16_t repl_bs = vdupq_n_u8 ('\\');
-  const uint8x16_t repl_qm = vdupq_n_u8 ('?');
-  const uint8x16_t xmask = (uint8x16_t) vdupq_n_u64 (0x8040201008040201ULL);
+  uint8x16_t d, res, tab, m;
+  uint16x8_t t;
+  uint64_t mask;
+  m = vdupq_n_u8 (0xf);
+  tab = vld1q_u8 (table);
+  d = vld1q_u8 (s);
 
-#ifdef __ARM_BIG_ENDIAN
-  const int16x8_t shift = {8, 8, 8, 8, 0, 0, 0, 0};
-#else
-  const int16x8_t shift = {0, 0, 0, 0, 8, 8, 8, 8};
-#endif
-
-  unsigned int found;
-  const uint8_t *p;
-  uint8x16_t data;
-  uint8x16_t t;
-  uint16x8_t m;
-  uint8x16_t u, v, w;
-
-  /* Align the source pointer.  */
-  p = (const uint8_t *)((uintptr_t)s & -16);
-
-  /* Assuming random string start positions, with a 4k page size we'll take
-     the slow path about 0.37% of the time.  */
-  if (__builtin_expect ((AARCH64_MIN_PAGE_SIZE
-			 - (((uintptr_t) s) & (AARCH64_MIN_PAGE_SIZE - 1)))
-			< 16, 0))
-    {
-      /* Slow path: the string starts near a possible page boundary.  */
-      uint32_t misalign, mask;
-
-      misalign = (uintptr_t)s & 15;
-      mask = (-1u << misalign) & 0xffff;
-      data = vld1q_u8 (p);
-      t = vceqq_u8 (data, repl_nl);
-      u = vceqq_u8 (data, repl_cr);
-      v = vorrq_u8 (t, vceqq_u8 (data, repl_bs));
-      w = vorrq_u8 (u, vceqq_u8 (data, repl_qm));
-      t = vorrq_u8 (v, w);
-      t = vandq_u8 (t, xmask);
-      m = vpaddlq_u8 (t);
-      m = vshlq_u16 (m, shift);
-      found = vaddvq_u16 (m);
-      found &= mask;
-      if (found)
-	return (const uchar*)p + __builtin_ctz (found);
-    }
-  else
+  while (1)
     {
-      data = vld1q_u8 ((const uint8_t *) s);
-      t = vceqq_u8 (data, repl_nl);
-      u = vceqq_u8 (data, repl_cr);
-      v = vorrq_u8 (t, vceqq_u8 (data, repl_bs));
-      w = vorrq_u8 (u, vceqq_u8 (data, repl_qm));
-      t = vorrq_u8 (v, w);
-      if (__builtin_expect (vpaddd_u64 ((uint64x2_t)t) != 0, 0))
-	goto done;
+      t = (uint16x8_t) vceqq_u8 (vqtbl1q_u8 (tab, vandq_u8 (d, m)), d);
+      mask = vget_lane_u64 ((uint64x1_t)vaddhn_u16 (t, t), 0);
+      if (mask != 0)
+	return s + (__builtin_ctzl (mask & 0x1111111111111111UL) >> 2);
+      s += 16;
+      d = vld1q_u8 (s);
     }
-
-  do
-    {
-      p += 16;
-      data = vld1q_u8 (p);
-      t = vceqq_u8 (data, repl_nl);
-      u = vceqq_u8 (data, repl_cr);
-      v = vorrq_u8 (t, vceqq_u8 (data, repl_bs));
-      w = vorrq_u8 (u, vceqq_u8 (data, repl_qm));
-      t = vorrq_u8 (v, w);
-    } while (!vpaddd_u64 ((uint64x2_t)t));
-
-done:
-  /* Now that we've found the terminating substring, work out precisely where
-     we need to stop.  */
-  t = vandq_u8 (t, xmask);
-  m = vpaddlq_u8 (t);
-  m = vshlq_u16 (m, shift);
-  found = vaddvq_u16 (m);
-  return (((((uintptr_t) p) < (uintptr_t) s) ? s : (const uchar *)p)
-	  + __builtin_ctz (found));
 }
 
 #ifdef HAVE_SVE2
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.