You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
memchr is not necessarily implemented with SIMD #3
The idea is to load anywhere from 4-8 bytes at a time and use the above to check for zeros. You can do more bit hackery after the loop exits (because you're guaranteed to have found your 0*). But the main thing is that even if we're dealing with hardware that potentially has SIMD instructions, it'll struggle to outperform this loop:
while (!has_zero(ch32)) {
//increment pointer by 4 etc...
}
So for now I've decided to rely on strlen (https://github.com/steve-chavez/CParseC/issues/3) instead of adding optimizations myself but I might revisit in the future. (I'm still stabilizing the library API).
@steve-chavez just to point out, it appears that musl word by word scan is essentially this, with an added first step to handle alignment, although I'm not a fan of preprocessor defines.
I'd double check on their implementation, but they've got all the added parts I'd have commented on. They used void* to detect the native register size to essentially pick between the 4 vs 8 byte implementations, and they made a point of dealing with the alignment of the incoming pointer.
I'd probably skip the preprocessor mess, and maybe test/benchmark whether the trailing loop vs a single calculated step is better. I'd suspect working with the mask would be better. However I'd bet they made a point of testing that, so I'd be curious why they have the brute force loop as the end step.
//We may want to know if any byte in a word has a specific value. To do so, we can XOR the value to test with a word that has been filled with the byte values in which we're interested. Because XORing a value with itself results in a zero byte and nonzero otherwise, we can pass the result to haszero.#definehasvalue(x,n) \
(haszero((x) ^ (~0UL/255 * (n))))
//Stephen M Bennet suggested this on December 13, 2009 after reading the entry for haszero.
The (~0UL/255 * (n)) is a way to splat n as a byte into a long, essentially the idea here is that the original test works using xor operations to determine where a 0 is, ergo, we can take the input, xor it with a bye we're interested ahead of time, and now it effectively becomes a has_byte_value because we've transformed the input such that now it's offset in such a way n now behaves like 0.
// Essentially your simd algorithm but*for (size_ti=0; i<input_length; i++) {
uint32_ttest=0; // like your algorithm*uint32_tsplat_byte_from_input= (~0u / 255* (input[i]));
for (size_tj=0; j<test_characters_len/sizeof(uint32_t);j++) {
test=test | mask_byte_value((uint32_t*)any_of_these)[j], splat_byte);
}
if (test) //builtin_clz etc... etc.. return
}
Now it'd be kind of ridiculous to do this*, but in theory we can do a preprocessing phase with a buffer of 256 chars that are aligned, making sure that we repeat the last character to the next power of 2 boundary of interest. Then we can perform the simd-esque loop without worrying about alignment and the algorithm will work with just the above loop!
alignas(16) charaligned_input_buffer[256];
// copy the test_characters overuint32_ti=0;
for (;test_characters&&*test_characters;test_characters++,i++)
aligned_input_buffer[i] =*test_characters;
charlast_char=aligned_input_buffer[i-1];//need an input character!for (;i&0xf!=0; i++) {
aligned_input_buffer[i] =last_char;
}
You can do the same with your simd function. Take the inputs, copy them over to an aligned 256 byte buffer and you won't need a second loop!
Activity
Andersama commentedon Jul 6, 2026
Just a follow up, to cut down the code to a single loop, you'd work with the function like this:
Where the location of any 0 is masked by a 0x80. You only care about the leading one so performing a reverse bitscan is the last step.
Retrieves the one based index of the guaranteed zero*, shifted by 3. 32 -> 4.
This last step, in theory should be the optimized result of a simple bitscan loop.
add: CPC_USE_STRING_H for strlen on cpc_slice_from_cstr
add: CPC_USE_STRING_H for strlen on cpc_slice_from_cstr
steve-chavez commentedon Jul 12, 2026
Hey @Andersama, many thanks for the feedback!
You're right in that memchr is not necessarily SIMD, I've added a note in the README telling it's implementation specific.
Regarding
cpc_slice_from_cstr, it was intentionally kept very naive (a poor man's strlen). I've checked somestrlenimplementations:So for now I've decided to rely on
strlen(https://github.com/steve-chavez/CParseC/issues/3) instead of adding optimizations myself but I might revisit in the future. (I'm still stabilizing the library API).Thanks again for the pointers 😃
Andersama commentedon Jul 12, 2026
@steve-chavez just to point out, it appears that musl word by word scan is essentially this, with an added first step to handle alignment, although I'm not a fan of preprocessor defines.
I'd double check on their implementation, but they've got all the added parts I'd have commented on. They used
void*to detect the native register size to essentially pick between the 4 vs 8 byte implementations, and they made a point of dealing with the alignment of the incoming pointer.I'd probably skip the preprocessor mess, and maybe test/benchmark whether the trailing loop vs a single calculated step is better. I'd suspect working with the mask would be better. However I'd bet they made a point of testing that, so I'd be curious why they have the brute force loop as the end step.
steve-chavez commentedon Oct 12, 2026
Managed to have a non-memchr version of SIMD on #13, faster too!
I've also removed
strlenusage, although I've just gone back to the simple byte-by-byte scan, improving it is TODO.Andersama commentedon Oct 17, 2026
I see what you did there @steve-chavez, you can take the has_zero bithack so that you have a faster fallback even if there isn't simd support. See: https://graphics.stanford.edu/~seander/bithacks.html#ValueInWord
The
(~0UL/255 * (n))is a way to splat n as a byte into a long, essentially the idea here is that the original test works using xor operations to determine where a 0 is, ergo, we can take the input, xor it with a bye we're interested ahead of time, and now it effectively becomes ahas_byte_valuebecause we've transformed the input such that now it's offset in such a way n now behaves like 0.Now it'd be kind of ridiculous to do this*, but in theory we can do a preprocessing phase with a buffer of 256 chars that are aligned, making sure that we repeat the last character to the next power of 2 boundary of interest. Then we can perform the simd-esque loop without worrying about alignment and the algorithm will work with just the above loop!
You can do the same with your simd function. Take the inputs, copy them over to an aligned 256 byte buffer and you won't need a second loop!