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

Matt Turner mattst88@gmail.com
Fri Aug 14 03:52:37 GMT 2026


On Thu, Aug 13, 2026 at 6:35 PM Wilco Dijkstra <Wilco.Dijkstra@arm.com> wrote:
>
> Hi Matt,
>
> +      do
> +       {
> +         word = *++word_ptr;
> +         mask = find_ne_all (word, repeated_c);
> +       }
> +      while (mask == 0);
> +
> +      return (const char *) word_ptr - str + index_first (mask);
>
> And this should obviously be:
>
> do
>   {
>     word = *++word_ptr;
>   }
> while (word == repeated_c);
>
> return (const char *) word_ptr - str + index_first (find_ne_all (word, repeated_c));
>
> That makes the loop several times faster.

Applied. It's a clear win on alpha, where find_ne_all is cmpbge-based:
roughly 2x in the word loop at 2K and above, 1.7x at 512.

It makes no difference on x86_64, arm or powerpc. With find_ne_all
reduced to x1 ^ x2 there, GCC already hoists the mask out of the loop
and compares the words directly, so the generated code is
byte-for-byte identical either way. Still worth writing explicitly,
since alpha can't get there on its own.

Updated speedups 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

> Whether it is worth optimizing this at all is another question - are people actually
> using single-char strspn for performance critical things? Scanners certainly don't,
> so the most likely scenario might be skipping repeated slashes in path names...

Not common enough to call performance critical, but not rare either.
Debian Code Search finds 956 call sites where the accept string is a
one-character literal, against 4652 with any string literal and 8462
strspn calls in total, so about a fifth of the literal cases.

Sampling 160 of those: 93 pass " ", 29 pass "/", then a tail of ".",
"_", "0", ",". Your guess about path names is the second most common;
skipping whitespace is the first. systemd, gettext, busybox, openssh,
gnupg2 and the kernel all have instances.

Those are static call sites rather than a profile, though, and the
uses I looked at scan short runs: slashes in a path, spaces between
tokens. That's where this patch gains least, 1.3x on alpha and 1.9x on
x86_64 at 8 bytes. The large figures are for lengths nobody is passing
here. So the honest case is a modest win on a shape that recurs across
the archive, for 69 bytes of text on x86_64 and 128 on alpha, and
glibc has marked that branch __glibc_unlikely since 2016. If you'd
rather not carry it, dropping this patch leaves the first two standing
on their own.


More information about the Libc-alpha mailing list