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

Matt Turner mattst88@gmail.com
Fri Aug 14 03:54:45 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 here, so a NUL byte differs from it and the search for inequality
also stops at the end of the string.

The word loop compares against the broadcast character directly rather
than building a mask each iteration, and calls find_ne_all () once at the
end.  Where find_ne_all () is an exclusive or the compiler already
generated this, but alpha builds the mask with cmpbge, and the comparison
halves its loop.

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

Checked against strspn () for lengths 0 to 300 at every byte alignment,
for several accept characters, with the run ended both by NUL and by a
differing byte.  Run on alpha (EV68CB), powerpc64 big-endian, 32-bit arm,
aarch64 and x86_64, covering the cmpbge, cmpb, uqsub8 and generic C
string-fza.h implementations.  The riscv ones were built but not run.

Speedup over the scalar loop it replaces:

  length        8    32   128   512    2K    8K   32K
  Alpha EV68  1.3x  3.3x  5.2x 11.7x 15.2x 16.4x 16.9x
  i7-1370P    1.9x  2.8x  5.5x  4.0x  5.2x  6.9x  7.6x

Both are the best of seven timed runs of each implementation, each run
calibrated to at least 0.3 s so that the millisecond clock granularity on
alpha does not quantize the result, and built with -falign-functions=64
so that code placement does not dominate the short lengths.  The i7-1370P
reaches this code only where SSE4.2 is unavailable, since the generic C
string-fza.h is what it would use there.

Suggested-by: Wilco Dijkstra <Wilco.Dijkstra@arm.com>
---
 string/strspn.c | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git ./string/strspn.c ./string/strspn.c
index 9b90ae61e7..f8e869e176 100644
--- ./string/strspn.c
+++ ./string/strspn.c
@@ -18,6 +18,9 @@
 #include <string.h>
 #include <stdint.h>
 #include <libc-pointer-arith.h>
+#include <string-fza.h>
+#include <string-fzi.h>
+#include <string-shift.h>
 
 #undef strspn
 #ifndef STRSPN
@@ -33,9 +36,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);
+
+      /* Comparing the whole word is cheaper than building the mask on
+	 targets where find_ne_all () is more than an exclusive or.  */
+      do
+	word = *++word_ptr;
+      while (word == repeated_c);
+
+      return (const char *) word_ptr - str
+	     + index_first (find_ne_all (word, repeated_c));
     }
 
   /* Use multiple small memsets to enable inlining on most targets.  */
-- 
2.54.0



More information about the Libc-alpha mailing list