Iterating over bit sets quickly
alexharri.com
alexharri.com
Can't blame the author for not trying that though, it's probably one of the most obscure and rarely used library functions in JavaScript.
(I will bookmark this article for the next time I need to iterate over bitmaps LSB first though)
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
There's a lot of research into fast ways to do these in the practical compressed data structures community if folks like it.
I'm doubtful of this in general. Some older processors did have a very slow BSF/BSR implementation, so it may be the case there, but I wouldn't generally stick with that assumption these days. (and it's most certainly not the case on AMD Zen, given all its ALUs can do these bit ops)
AVX doesn't have lzcnt prior to AVX-512 CD, so it could make more sense there, though I'd imagine that abusing float conversion (convert to float, extract exponent) would still be faster.
Hi, I'm folks who like it :). Any (other) keywords and authors I should search for?
I guess it means a js branch is very expensive even when predictable but that feels slightly unsatisfactory.
The original implementation has 2 branches per iteration: one for the loop, and one to test whether the bit is set or not.
The optimized implementation has 1 branch per iteration.
I doubt that the fact that it's JS matters all that much given that either loop is absolutely tiny once it's compiled down to optimized assembly.
It might matter. At least when run in the browser, JS needs to be interruptible (so pages don't hang on infinite loops), which is often done by checking a flag at the target of any backwards branch. So that's another (predictable) branch.
And sometimes when you get enough branches, they have an effect even if they're near 100% predictable. I don't know if it's from branch aliasing, or using up general branch prediction resources, or prefetching / some other optimization giving up after too many branches, or what.
Your intuition seems correct, because the optimized version is slower for me - both on Firefox nor on Chrome. But using the Math.clz32 version I suggested elsewhere is always faster.
One other issue I have is with using () => {} as the callback. So I added a stateful callback with a small side-effect to verify whether or not this loop optimization even matters, or whether the callback overhead should be expected to dwarf it. The results do suggest optimizing the loop is worth it.
I also tried using a Uint32Array backing array, which has identical performance! That is a good sign regarding plain array optimizations[1].
On lower values the optimized options are faster, as expected although the clz32 does significantly better, and in the case of Firefox helps it (almost) catch up to Chrome.
[0] https://jsbenchit.org/?src=219dd9ed01e98533590fc8de40c886f4 fully set
[1] https://jsbenchit.org/?src=b82e3a2e9631f5e781fc9ff796072985 fully set, typed array
[2] https://jsbenchit.org/?src=2dc59964ac15bbc22d58f1f9ee0a6d3e 50% randomly set
[3] https://jsbenchit.org/?src=3faa2eb84da523890923549c3b316f2e 25% randomly set in lower half
[4] https://jsbenchit.org/?src=646bf8088d0cd2808e236518b7eaa944 one random bit set per word
[5] https://jsbenchit.org/?src=4982f7e3557153267e23b4a88732d6a4 one random bit set every two words
[6] https://jsbenchit.org/?src=e4ac3cb90b4a395dd7a873fd51267d48 empty bitset (sanity check)
I wonder what OP was measuring.
Oh and same for saturating arithmetic.
Meanwhile, you might appreciate the venerable https://graphics.stanford.edu/~seander/bithacks.html