Commit cd259272c8 for perl
commit cd259272c85e6878239ce1ff2fc8402a661b74fe
Author: Richard Leach <rich+perl@hyphen-dash-hyphen.info>
Date: Fri Oct 2 16:07:28 2026 +0000
S_find_next_masked: widen search to check 512 bits at a time
The following were used to test throughput of this function:
* string: `my $run_nanyofm = ('B' x 2_000_000) . 'A';`
* pattern: `$run_nanyofm =~ /\A[^A]+/;`
The following results were seen:
Rate
blead (doesn't scale with -march) 4741/s
this commit, no -march 16365/s
this commit, -march=x86-64-v2 16290/s
this commit, -march=x86-64-v3 27534/s
(No CPU to hand that supports -march=x86-64-v4.)
diff --git a/regexec.c b/regexec.c
index 3b5e09fa8d..f3896cb924 100644
--- a/regexec.c
+++ b/regexec.c
@@ -791,22 +791,48 @@ S_find_next_masked(U8 * s, const U8 * send, const U8 byte, const U8 mask)
/* Returns the position of the first byte in the sequence between 's'
* and 'send-1' inclusive that when ANDed with 'mask' yields 'byte';
- * returns 'send' if none found. It uses word-level operations instead of
- * byte to speed up the process */
+ * returns 'send' if none found. */
- const U8 * const per_byte_end = WORTH_PER_WORD_LOOP(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) == 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++) {
+ U8 d = (U8) (((U8) (s[k] & mask)) ^ byte); /* 0 iff match */
+ hit |= (U8) (d == 0 ? 0xFF : 0x00);
+ }
+ if (hit) {
+ 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 word = PERL_COUNT_MULTIPLIER * 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' contains bytes with the bit pattern of 'byte' within
@@ -838,7 +864,7 @@ S_find_next_masked(U8 * s, const U8 * send, const U8 byte, const U8 mask)
s += variant_byte_number(masked);
return s;
- } while (s + PERL_WORDSIZE <= send);
+ }
}
while (s < send) {