Commit 201f8152c5 for perl
commit 201f8152c500bcab2066e75b214d7e0f4ded342d
Author: Richard Leach <rich+perl@hyphen-dash-hyphen.info>
Date: Fri Oct 2 10:11:10 2026 +0000
S_find_span_end_mask: widen search to check 512 bits at a time
The following were used to test throughput of this function:
* string: `my $run_anyofm = ('0' x 2_000_000) . '.';`
* pattern: `$run_anyofm =~ /\A[01]+/;`
The following results were seen:
Rate
blead (doesn't scale with -march) 9394/s
this commit, no -march 16140/s
this commit, -march=x86-64-v2 20003/s
this commit, -march=x86-64-v3 27659/s
(No CPU to hand that supports -march=x86-64-v4.)
diff --git a/regexec.c b/regexec.c
index c96eb9840f..3b5e09fa8d 100644
--- a/regexec.c
+++ b/regexec.c
@@ -863,19 +863,45 @@ S_find_span_end_mask(U8 * s, const U8 * send, const U8 span_byte, const U8 mask)
* function. Returns 'send' if none found. Works like find_span_end(),
* except for the AND */
- const U8 * const per_byte_end = WORTH_PER_WORD_LOOP_BINMODE(s, send, 1);
- if (per_byte_end) {
+ /* Walk leading bytes until "s" is word-aligned, for the benefit of those
+ * platforms where unaligned access is much slower than aligned access. */
+ {
+ const U8 * const word_end = s + BYTES_REMAINING_IN_WORD(s);
+ const U8 * const per_byte_end = (word_end < send) ? word_end : send;
while (s < per_byte_end ) {
if (((*s) & mask) != span_byte) {
return s;
}
s++;
}
+ }
+ /* Fast path: fixed-length masked reduction per PERL_FSE_SIZE block.
+ * This should vectorize given a capable compiler and compile target(s).
+ * Testing on an AMD Zen processor showed codegen improving and
+ * throughput increasing from:
+ * bog standard build -> -march=x86-64-v2 -> -march=x86-64-v3
+ */
+ if (WORTH_PER_WORD_LOOP_BINMODE(s, send, PERL_FSE_SIZE / PERL_WORDSIZE)) {
+ while (s + PERL_FSE_SIZE <= send) {
+ U8 hit = 0;
+ unsigned k;
+ for (k = 0; k < PERL_FSE_SIZE; k++) {
+ hit |= (U8) (((U8) (s[k] & mask)) ^ span_byte);
+ }
+ if (hit != 0) {
+ break; /* Let the per-word code find the first hit */
+ }
+ s += PERL_FSE_SIZE;
+ }
+ }
+
+ /* Process per-word as long as we have at least a full word left */
+ if (WORTH_PER_WORD_LOOP_BINMODE(s, send, 1)) {
PERL_UINTMAX_T span_word = PERL_COUNT_MULTIPLIER * span_byte;
PERL_UINTMAX_T mask_word = PERL_COUNT_MULTIPLIER * mask;
- do {
+ while (s + PERL_WORDSIZE <= send) {
PERL_UINTMAX_T masked = (* (PERL_UINTMAX_T *) s) & mask_word;
if (masked == span_word) {
@@ -888,7 +914,7 @@ S_find_span_end_mask(U8 * s, const U8 * send, const U8 span_byte, const U8 mask)
masked |= masked << 2;
masked |= masked << 4;
return s + first_upper_bit_set_byte_number(masked);
- } while (s + PERL_WORDSIZE <= send);
+ }
}
while (s < send) {