Who doesn’t love a little micro-optimization? I love messing around with in my side projects to push the performance as far as I can. I’m currently working on an RSS feed reader which uses IndexedDB, and as it turns out IDB doesn’t allow boolean values to be indexed.
This meant that I had to start representing a boolean value as the numbers 0 or 1, and this raised an important question: what is the absolutely blazing-est fast-est-est way to coerce a boolean value into an integer in JavaScript?
The setup
First, let’s all say it together: micro-benchmarks are bad and rarely a good measure of anything. I tried my best to get something meaningful out of this, bu all results here should be taken with a grain of salt.
I used https://jsbench.me to set up my tests and ran them in every browser. First, I have a setup script that executes before every run:
// Make an array of a random size between 128-256
const bools = new Array(128 + Math.floor(Math.random() * 128));
// Randomly fill the array with boolean values
for (let i = 0; i < bools.length; ++i) {
bools[i] = Math.random() < 0.5;
}
Then I had each test case loop over the bools array and use a different method to coerce each value into an integer:
// Ternary
for (const bool of bools) {
const num = bool ? 1 : 0;
}
// Casting with Number constructor
for (const bool of bools) {
const num = Number(bool);
}
// Bitwise operator
for (const bool of bools) {
const num = bool & 1;
}
Going into this, my hypothesis was that the ternary operator would perform the worst since it involves branching, then casting would be a little faster, and the bitwise operation would be a little faster still.
The results
We will use the ternary operator as our baseline, as that’s what I would consider the most “readable” approach which I would actually use in a shared codebase where I’m worried about maintainability. I ran these tests in 3 different browsers and was pretty surprised by the results.
| Browser | Ternary | Casting | Bitwise |
|---|---|---|---|
| Chrome | 584K ops/s | 752K ops/s (+29%) | 1M ops/s (+78%) |
| Firefox | 1.8M ops/s | 439K ops/s (-75%) | 1.8M ops/s (-) |
| Safari | 3.2M ops/s | 3M ops/s (-) | 3.1M ops/s (-) |
Say what you will about Safari but boy is it fast. Not only did all 3 results fall within the margin of error of each other, they also all ran over 3-5 times faster than Chrome!
Firefox didn’t do too bad either, with the ternary and bitwise cases both running very fast and
staying within the margin of error of each other. However, casting with the Number constructor
did very poorly in Firefox, producing the slowest results out of any of the tests run.
And so that leaves us with Chrome. These results come the closest to what my expectations had been, but even then I was surprised by how big the performance gap was between each approach. I certainly did not expect bitwise operations to outperform ternary operators by nearly 2x!
Negation
Now I was curious, does this look any different in cases where we want to negate the value so that
true becomes 0 and false becomes 1? I wrote some new test cases to see:
// Ternary
for (const bool of bools) {
const num = bool ? 0 : 1;
}
// Casting with Number constructor + NOT
for (const bool of bools) {
const num = Number(!bool);
}
// Casting with Number constructor + subtraction
for (const bool of bools) {
const num = 1 - Number(bool);
}
// Bitwise operator
for (const bool of bools) {
const num = bool ^ 1;
}
| Browser | Ternary | Casting + NOT | Casting + Subtraction | Bitwise |
|---|---|---|---|---|
| Chrome | 586K ops/s | 455K ops/s (-22%) | 679K ops/s (+16%) | 1.1M ops/s (+87%) |
| Firefox | 1.7M ops/s | 430K ops/s (-75%) | 428K ops/s (-75%) | 1.7M ops/s (-) |
| Safari | 2.9M ops/s | 3.1M ops/s (-) | 3M ops/s (-) | 3.1M ops/s (-) |
The results look pretty similar to last time, but I was interested to see that Chrome performs
significantly worse when combining a Number cast with a NOT operator. I’m not sure why that would
be, but that difference has been very consistent across multiple runs of these benchmark tests.
Conclusions
So, what can be taken away from this? Overall, the results are a little inconsistent between each browser engine’s implementation, but we can clearly see that the bitwise operator is always either the outright winner or tied for first place in every browser.
However, that’s obviously by far the least readable of all of the solutions. Even if a bitwise operation is nearly 2 times faster in Chrome than the more readable ternary operator, we need to think at scale here; the percentage looks impressive, but the actual difference is probably somewhere around 1-2 nanoseconds. There are much higher impact things you could be focusing on instead.
If you somehow find yourself solving a problem where you have thousands of boolean values which all need to be converted to integers in bulk, switching to a bitwise operator might be the right move for you. Otherwise, I wouldn’t consider it to be very practical.
That being said, I’m going to keep using it in my side projects and you can’t stop me.