Skip to content

gh-161145: Test more bytes simultaneously in find_first_nonascii - #161147

Open
cmaloney wants to merge 2 commits into
python:mainfrom
cmaloney:find_first_nonascii_8w
Open

cmaloney wants to merge 2 commits into
python:mainfrom
cmaloney:find_first_nonascii_8w

Conversation

@cmaloney

@cmaloney cmaloney commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Long ASCII runs now OR eight aligned words and test the high bits once, one branch per 64 bytes (32 on 32-bit) instead of one per word. When a block contains a non-ASCII byte, the existing one-word loop finds it. It is plain C: GCC and Clang compile it to SSE2/NEON loads, other compilers to ORed word loads with the same single branch.

Multiple compiler and architecture codegen sample: https://godbolt.org/z/8bM83vx3a

pyperf, PGO+LTO, 2 interleaved rounds:

Benchmark main this
(b'a' * 4096).decode() 165 ns 108 ns: 1.53x faster
(b'a' * 200).decode() 28.9 ns 26.5 ns: 1.09x faster
b'a' * 200 + 'é' + ..., .decode() 60.9 ns 56.0 ns: 1.09x faster
b'a' * 136 + 'é' + ..., .decode() 53.8 ns 51.3 ns: 1.05x faster
8-100 byte decodes, 'é' within 100 within 1.2 ns
import pyperf

runner = pyperf.Runner()
tail = 'é'.encode() + b'a' * 50
for n in (8, 100, 200, 4096):
    runner.timeit(f"(b'a' * {n}).decode()", "s.decode()",
                  globals={"s": b'a' * n})
for n in (16, 100, 136, 200):
    runner.timeit(f"(b'a' * {n} + 'é'.encode() + b'a' * 50).decode()",
                  "s.decode()", globals={"s": b'a' * n + tail})

Long ASCII runs now OR eight aligned words and test the high bits once,
one branch per 64 bytes (32 on 32-bit) instead of one per word.  When
a block contains a non-ASCII byte, the existing one-word loop finds it.
It is plain C: GCC and Clang compile it to SSE2/NEON loads, other
compilers to ORed word loads with the same single branch.

Multiple compiler and architecture codegen sample: https://godbolt.org/z/8bM83vx3a

pyperf, PGO+LTO, 2 interleaved rounds:

| Benchmark | main | this |
|---|---:|---:|
| (b'a' * 4096).decode() | 165 ns | 108 ns: 1.53x faster |
| (b'a' * 200).decode() | 28.9 ns | 26.5 ns: 1.09x faster |
| b'a' * 200 + 'é' + ..., .decode() | 60.9 ns | 56.0 ns: 1.09x faster |
| b'a' * 136 + 'é' + ..., .decode() | 53.8 ns | 51.3 ns: 1.05x faster |
| 8-100 byte decodes, 'é' within 100 | | within 1.2 ns |

```python
import pyperf

runner = pyperf.Runner()
tail = 'é'.encode() + b'a' * 50
for n in (8, 100, 200, 4096):
    runner.timeit(f"(b'a' * {n}).decode()", "s.decode()",
                  globals={"s": b'a' * n})
for n in (16, 100, 136, 200):
    runner.timeit(f"(b'a' * {n} + 'é'.encode() + b'a' * 50).decode()",
                  "s.decode()", globals={"s": b'a' * n + tail})
```

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cmaloney cmaloney removed the skip news label Nov 2, 2026
@cmaloney cmaloney changed the title gh-161145: Test more bytes simultaneously find_first_nonascii gh-161145: Test more bytes simultaneously in find_first_nonascii Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting review type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant