Strings in programming are often represented as arrays of 8-bit words. The string is ASCII if and only if all 8-bit words have their most significant bit unset. In other words, the byte values must be no larger than 127 (or 0x7F in hexadecimal).
A decent C function to check that the string is ASCII is as follows.
bool is_ascii_pessimistic(const char *data, size_t length) {
for (size_t i = 0; i < length; i++) {
if (static_cast<unsigned char>(data[i]) > 0x7F) {
return false;
}
}
return true;
}
We go over each character, we compare it with 0x7F and continue if the value is no larger than 0x7F. If you have scanned the entire string and all tests have passed, you know that your string is ASCII.
Notice how I called this function pessimistic. What do I mean? I mean that it expects, in some sense, that it will find some non-ASCII character. If so, the best option is to immediately return and not scan the whole string.
What if you expect the string to almost always be ASCII? An alternative then is to effectively do a bitwise OR reduction of the string: you OR all characters together and you check just once that the result is bounded by 0x7F. If any character has its most significant bit set, then the bitwise OR of all characters will also have its most significant bit set. So you might write your function as follows.
bool is_ascii_optimistic(const char *data, size_t length) {
unsigned char result = 0;
for (size_t i = 0; i < length; i++) {
result |= static_cast<unsigned char>(data[i]);
}
return result <= 0x7F;
}
If you have strings that are all pure ASCII, which function will be fastest? Maybe surprisingly, the optimistic might be several times faster. I wrote a benchmark and ran it with GCC 15 on an Intel Ice Lake processor. I get the following results.
| function | speed |
|---|---|
| pessimistic | 1.8 GB/s |
| optimistic | 13 GB/s |
Why is the optimistic faster? Mostly because the compiler is better able to optimize it. Among other possibilities, it can use autovectorization to automatically use data-level parallelization (e.g., SIMD instructions).
Which function is best depends on your use case.
What if you would prefer a pessimistic function, that is, one that returns early when non-ASCII characters are encountered, but you still want high speed? Then you can use a dedicated library like simdutf where we have hand-coded the logic. In simdutf, the pessimistic function is called validate_ascii_with_errors. Your results will vary but I got that it has the same speed as optimistic function.
| function | speed |
|---|---|
| pessimistic | 1.8 GB/s |
| pessimistic (simdutf) | 14 GB/s |
| optimistic | 13 GB/s |
So it is possible to combine the benefits of pessimism and optimism although it requires a bit of care.
Daniel Lemire, "Performance trick : optimistic vs pessimistic checks," in Daniel Lemire's blog, December 20, 2025, https://lemire.me/blog/2025/12/20/performance-trick-optimistic-vs-pessimistic-checks/.
[BibTeX]
I think the distinction between pessimism and optimism is somewhat misleading.
It’s worth explaining why exactly the compiler can’t vectorize
is_ascii_pessimistic. The compiler cannot assume that any offseti < lengthis dereferenceable: for example, you can callis_ascii_pessimistic("\xff", 100)without committing UB. Autovectorization would have added UB to a UB-free program. If you know the array is safe to dereference forlengthbytes, you can annotate it with__builtin_assume_dereferenceableand get vectorized code: https://godbolt.org/z/M4GM7rPEn. (I guess a compiler could align the pointer to 16 bytes with scalar code and then rely on page boundaries a la strlen, but eugh. Something something non-composable optimizations.)It might be *slightly* worse than the optimistic version due to checks within the loop, depending on your problem, so it’s still a useful trick to keep in mind. But I think this was not the best example.
One thought on simdutf is that you can use max instead of or to get the highest byte value, which will indicate ASCII and also the longest UTF8 multibyte sequence if any.
Definitely. We would add this if there was demand for it.
AMD Ryzen AI 9 HX PRO 370 w/ Radeon 890M (2.00 GHz)
Microsoft Visual C++ 2026
optimistic : 0.019 ns/byte 52.23 GB/s
simdutf_optimistic : 0.019 ns/byte 52.63 GB/s
pessimistic : 0.197 ns/byte 5.08 GB/s
simdutf_pessimistic : 0.019 ns/byte 52.59 GB/s
Yes so a 10x factor on an AMD Zen 4 microarchitecture.