[PATCH 2/2] string: vectorize strspn single-accept-char case

Matt Turner mattst88@gmail.com
Thu Aug 13 20:07:42 GMT 2026


The single-accept-character fast path was a byte-at-a-time scalar loop.
Scan a word at a time instead, using find_ne_all () to locate the first
byte that differs from the broadcast accept character.  ACCEPT[0] is not
NUL there, so a NUL byte differs from it and the search for inequality
also stops at the end of the string.

The multi-character case keeps the existing bitmap-table scan.

Checked against strspn () for lengths 0 to 300 at every byte alignment,
with the run ended both by NUL and by a differing byte, on x86_64, s390x,
powerpc64, arm and alpha, covering the generic, cmpb, uqsub8 and cmpbge
string-fza.h implementations.  The riscv one was built but not run.

Speedup over the scalar loop it replaces, on an Alpha EV68 and on a Core
i7-1370P (which uses the generic C string-fza.h, and only reaches this
code where SSE4.2 is unavailable):

  length       8    32   128   512    2K    8K   32K
  Alpha EV68 1.4x  3.3x  4.5x  5.7x  6.4x  6.6x  6.6x
  i7-1370P   0.8x  1.6x  4.3x  3.6x  2.7x  2.8x  2.9x

Below 16 bytes on x86_64 the broadcast and the unaligned first word cost
more than the scalar loop, which the uniform benchmark input lets the
branch predictor run at nearly a byte per cycle; the loss there is under
half a nanosecond per call.
---
 string/strspn.c | 27 ++++++++++++++++++++++++---
 1 file changed, 24 insertions(+), 3 deletions(-)

diff --git ./string/strspn.c ./string/strspn.c
index 9b90ae61e7..2dea03704a 100644
--- ./string/strspn.c
+++ ./string/strspn.c
@@ -18,6 +18,10 @@
 #include <string.h>
 #include <stdint.h>
 #include <libc-pointer-arith.h>
+#include <string-fza.h>
+#include <string-fzc.h>
+#include <string-fzi.h>
+#include <string-shift.h>
 
 #undef strspn
 #ifndef STRSPN
@@ -33,9 +37,26 @@ STRSPN (const char *str, const char *accept)
     return 0;
   if (__glibc_unlikely (accept[1] == '\0'))
     {
-      const char *a = str;
-      for (; *str == *accept; str++);
-      return str - a;
+      /* Skip the bytes equal to ACCEPT[0] a word at a time.  ACCEPT[0] is
+	 not NUL here, so a NUL byte differs from it and the search for
+	 inequality also stops at the end of the string.  */
+      const uintptr_t s_int = (uintptr_t) str;
+      const op_t *word_ptr = (const op_t *) PTR_ALIGN_DOWN (str, sizeof (op_t));
+      const op_t repeated_c = repeat_bytes (accept[0]);
+
+      op_t word = *word_ptr;
+      find_t mask = shift_find (find_ne_all (word, repeated_c), s_int);
+      if (mask != 0)
+	return index_first (mask);
+
+      do
+	{
+	  word = *++word_ptr;
+	  mask = find_ne_all (word, repeated_c);
+	}
+      while (mask == 0);
+
+      return (const char *) word_ptr - str + index_first (mask);
     }
 
   /* Use multiple small memsets to enable inlining on most targets.  */
-- 
2.54.0



More information about the Libc-alpha mailing list