Reputation: 8467
When checking if a value x
is a boolean, is
typeof x === 'boolean'
faster than x === true || x === false
or vice-versa?
I expected that literal comparison would be faster, but it seems like the typeof comparison is almost twice as fast.
Sidenote: I know this doesn't matter for almost any practical purpose.
Here is the benchmark code (disclaimer: I don't know how to benchmark): https://jsperf.com/check-if-boolean-123
Upvotes: 3
Views: 1108
Reputation: 138257
It depends.
To give an absolute answer, we would have to compile every piece of code / watch the interpreter on every possible browser / architecture combination. Then we could give an absolute answer which operation takes fewer processor cycles, everything else is pure speculation. And that's what I'm doing now:
The naive approach
Let's assume that engines do not perform any optimizations. That they just perform every step as defined in the specification. Then for every testcase the following happens:
typeof x === 'boolean'
(1) The type of x
gets looked up. As the engine probably represents a generic "JavaScript Value" as a structure with a pointer to the actual data and an enum for the type of the value, getting a string describing the type is probably a lookup in a type -> type string map.
(2) Now we have two string values, that we have to compare ('boolean' === 'boolean'
). First of all, it has to be checked that the types equal. That is probably done by comparing the type field of both values, and a bitwise equality (meaning: one processor op).
(3) Finally the value has to be compared for equality. For strings this means iterating over both strings and comparing the characters to each other.
x === true || x === false
(1) First of all, the types of x
and true
and x
ad false
have to be compared as described above.
(2) Secondly the values have to be compared, for booleans that's bitwise quality (meaning: one processor op).
(3) The last step is the or expression. Given two values, they first have to be checked for truthiness (which is quite easy for booleans, but still we have to check again that these are really booleans), then the or operation can be done (bitwise or, meaning: one processor op).
So which one is faster? If I had to guess, the second approach, as the first one has to do string equality, which probably takes a few iterations more.
The optimal approach
A very clever compiler might realize that typeof x === 'boolean'
is only true if the type of x is boolean. SO it could be optimized to (C++ pseudocode):
result = JSValue( JSType::Boolean, x->type == JSType::Boolean)
This is just a few processor ops, so it is really fast. In comparison to the naive approach, we save a string comparison and multiple typechecks. Will the engine do such optimization? Probably, because typeof
checks are quite common, and are quite easy to optimize (so it's an easy win).
Can we optimize x === true || x === false
? Well, yes, as we know the type of true
and false
this can be boiled down to (C++ pseudocode):
var result = JSValue(JSType:Boolean, x->type == JSType:Boolean && !x->value || x->type == JSType:Boolean && x->value)
Can't it be optimized further? Can't the compiler see that x->value
and !x->value
are mutually exclusive, and therefore it is actually exactly the same as the code above. Will the compiler do that optimization? I don't know. It is definetly not that easy, and optimizing x || !x
is actually an easy optimization the developer can take.
So in a perfect world, with a very thoughtful compiler, the first one will be faster.
But will the compiler optimize that much? It depends. Today, engines take the naive approach first (cause compilation does cost time too), and will only optimize if a function gets hot (gets called often). That's the case in your testcases (that's why the second one is faster). Wether real world code gets hot really depends on the usecase.
takeaway
On my device the first version performs 451,701,256 ops / s (!!). Is that fast? YES!
The second version performs at 198,308,952 ops / s. Is that twice as slow? Yes. Is it slow? NO! There are probably other pieces of code that consume much more processor cycles.
further thoughts
A very common optimization is to compile down functions after deducing the datatypes of the arguments. That means if you do
const check = it => typeof it === "boolean";
for(let i = 0; i < 100000; i++) check(true); // make function hot
The function might get compiled down to
boolean check(/*deduced datatype*/ boolean it) {
return true; // <- even faster than anything above
}
Now what happens if you call check("ouch")
? Well, at the entrypoint of the function, the types get asserted, the assertion fails, and the engine has to fall back to interpretation / recompile the function. Therefore calling the function 100000 times with booleans and then 100000 times with strings is probably faster than calling the function 200000 times with strings / booleans randomly. Keep this in mind when writing performance tests.
Upvotes: 6