Bitwise operators are faster. Avoid these micro-optimizations
TL;DR: Don't use bitwise operators unless your business model is bitwise logic.
Problems
Readability
Clevereness
Premature Optimization
Maintainability
Bijection Violation
Solutions
- Improve readability
Context
Some clever programmers solve problems we don't have.
We should optimize code based on evidence and use the scientific method.
We should benchmark only if necessary and improve code only if really necessary and bear the cost of changeability and maintainability.
Sample Code
Wrong
const nowInSeconds = ~~(Date.now() / 1000)
Right
const nowInSeconds = Math.floor(Date.now() / 1000)
Detection
[X] Semi-Automatic
We can tell our linters to warn us and manually check if it is worth the change.
Exceptions
- Real-time and mission-critical software.
Tags
- Premature Optimization
Conclusion
If we find this code in a pull request or code review, we need to understand the reasons. If they are not justified, we should do a rollback and change it to a normal logic.
Relations
Code Smell 20 - Premature Optimization
Maxi Contieri ・ Nov 8 '20
Code Smell 165 - Empty Exception Blocks
Maxi Contieri ・ Sep 24 '22
More Info
Disclaimer
Code Smells are just my opinion.
Credits
Photo by Frédéric Barriol on Unsplash
Original Article Here.
Watch the little things; a small leak will sink a great ship.
Benjamin Franklin
Software Engineering Great Quotes
Maxi Contieri ・ Dec 28 '20
This article is part of the CodeSmell Series.
Top comments (29)
You can use the JS bitwise operator "OR" once -->
var x = y | 0
this is much more efficient for computation, it do exactly as the Math.floor() function except you don't have to go trough the "Math" object and so on... (it is used in asm.js)why?
Please tell me your reasons to use the operator instead of the more declarative Math.foor ?
Do you have strong evidence this is really necessary ?
Yes because formerly it doesn't have to access the Math object to perform the operation and it is used like this not humanly though but in evidence in asm.js... both are correct, I mean one is best for readability and one is just a "trick" to force the variable being coerced into an integer, mostly interesting Low-Level-JavaScript (LLJS) explain that it is best suited like this for code that should be optimized in terms of entire number.
Relevant question is the same: Why it should be optimized?
Because computation on CPU can be slow...
I had a pixel art project, which enable one to draw on images and it does all the color blending computation in JS, which written in ASM.JS style is as much fast as it can be in webassembly, as I don't understand how I could make it work faster in WebGL...
ok. there are a few cases were you need to optimize the code.
There the 'Exceptions' in the article.
Most code does not need these optimizations
Yes
Hey but it’s clever and clever is not good in teams or in the world of corporate programming. Example; I’m 30ish and I’m now cognitively challenged due to my advanced age I need simple code if I’m in your team I don’t want the cost of going wtf StackOverflow, not seen this in my gosh dang whole career 😬😡
See it’s not worth it because time is money
I am over 50.
And I am tired of young people programming like in the 1960s.
Is this why you have this wonderful series? I have been keenly following.
If I’m not mistaken you can explain the GOTO statement 😁
of couse I can
Code Smell 100 - GoTo
Maxi Contieri ・ Nov 6 '21 ・ 2 min read
Since JavaScript Numbers are double-precision floating-point values, it shouldn't even support bitwise operators. It leads to mathematical nonsense such as
y | 0
not necessarily equallingy
, and likewise~~y
not necessarily equallingy
.Indeed
This is also an example of Javascript magic conversions
Code Smell 69 - Big Bang (JavaScript Ridiculous Castings)
Maxi Contieri ・ May 4 '21 ・ 2 min read
Numbers aren't double-precision floating-point values when one explicitely and systematically tells the compiler
| 0
hey here is a integer(x | 0)>>>0
hey here is the same but positive, instead if our number is bigger than the maximum value of a 32 bits Unsigned integer, using>>>0
force the compiler to use, excuse me, "no doubles" for it. (in certain case ^^)JavaScript's Number type is a IEEE 754 double-precision floating-point. developer.mozilla.org/en-US/docs/W...
JavaScript's bitwise operators on floating point Numbers coerces it to a 32bit integer (with a call to ToInt32) and then applies the bitwise op to that integer. In this way, bitwise ops on a floating-point type is pure nonsense. The op isn't being used for the actual logical use of the bitwise op. It is being used to gain access to an internal side-effect, and an internal type not otherwise exposed to the JS programmer.
That's not what the asmjs.org/ project did (from Mozilla), it wasn't non-sense at all, and all of this enabled porting C/C++ algorithms into JavaScript through Emscripten. Look, no, it is not nonsense, it is a missleading conception to think JS code isn't optimized within the JavaScript Engine. You hadn't being forced to use ASM.JS code alike for performance my friend, you should having been seen the results.
Not sure what you mean by "not what asmjs.org did". Numbers in JS are according to the JS spec, floating-point. Period. There is a BigInt type but that isn't involved in any way when applying bitwise ops to Numbers. If you are referring to what happens when one compiles C to JS, then whatever JS is produced isn't intended for human readability. The JS is essentially acting as a sort of machine code in that instance, and shouldn't be used as a baseline for writing JS that is intended for human readability.
The spec also defines the abstract operations
ToInt32(argument)
ToUint32(argument)
returning an integral number, i.e. it ceases to be an IEEE 754 floating point value but is just a signed or unsigned 32 bit string that is handled by the CPU's typical bitwise operations.
For example
NumberBitwiseOp(op,x,y)
convertsx
andy
toInt32
(i.e. doesn't operate on them as IEEE 754 floating-point values).And the reality is that JavaScript runtimes are allowed to optimize values. For example V8 can handle an array of integers as
PACKED_SMI_ELEMENTS
(small integers, Int32; Element Kinds).The main point is still the same: using a bitwise op as a clever trick to call the abstract ToInt32, when you don't really actually need the bitwise operation itself, only obfuscates your logic, negatively impacting readability and maintainability.
exactly
That is not a good reason to reject bitwise operations wholesale and much less a justification to stay ignorant about their semantics.
“clever, adjective
…
1b: mentally quick and resourceful but lacking in depth and soundness”
from: Webster's Ninth New Collegiate Dictionary (1983; ISBN 0919028667)
And
reflects that unsound thinking.
Clearly an
Int32
with a range of -2147483648…2147483647 cannot cover the domain of values -8.64e12…8.64e12 that(Date.now() / 1000)
is capable of producing.About bitwise operations
Andrea Giammarchi ・ Oct 8 '21 ・ 10 min read
Your explanation is fine if you need bitwise logic.
The sample from the code smell has nothing to do with bitwise logic.
I was primarily responding to:
which suggests that bitwise operators should be summarily banned from usage.
Ultimately bitwise operators do not operate on floating point numbers; the 32-bit bit strings they do operate on simply use the IEEE 754 binary64 as a conveyance.
There is an issue in TypeScript to add a
BitwiseInt32
type to clearly differentiate that usage fromnumber
.To me the sample code is indicative of a much bigger problem which is only tangentially related to bitwise operators; using code without fully understanding its limitations.
Aside from the readability issues, the statement that
~~(value)
andMath.trunc(value)
are equivalent is nonsense:Math.trunc()
works reliably without loss of precision over the rangeNumber.MIN_SAFE_INTEGER
…Number.MAX_SAFE_INTEGER
Now some people may argue that most JavaScript code will not need to use bitwise operators, so it should be treated as an advanced topic (that may be more useful in server side code).
However lots of interview preparation materials (e.g. Elements of Programming Interview in python) have plenty of exercises related to manipulating bit strings.
The article seems to unfairly single out bitwise operations when the actual issues are:
Using 2 bitwise complements as a means of calling ToInt32 a function that isn't otherwise directly exposed is sneaky. "Clever" lacks the negative connotation of "sneaky". So you are correct that it isn't clever. It is sneaky. Clever is just a nicer way of saying it.
@peerreynders the BitwiseInt32 proposal for TypeScript looks like a good way to add the missing semantics inherent in applying bitwise ops, not because you need bitwise logic, but because you are trying to utilize the side-effect of the op for type coercion.
Understanding bit manipulation, such as the article that you linked to on interview prep, can be very important. You'll notice that article is Python focused. I work with genetic algorithms a lot. Bit manipulation is extremely important. I mostly use Java for my GA work but occasionally Python, long ago C. Genetic algs is a case where bitwise ops are used because you need bitwise logic. In a GA, you aren't using a bitwise op to accomplish something else, such as the various ways they are used in JS for type coercion as a micro-optimization rather than just using a call to the more semantic trunc.
I think a title of Code Smell 180 - Micro Optimizations would have been more appropriate.
The whole idea of micro-optimizations is that they only really apply to code that has been proven to be on the hot path. From that perspective the sample code qualifies because under most circumstances using
Math.trunc()
wouldn't be pessimizing prematurely.Working with bit strings in 32-bit chunks isn't ideal but sometimes you gotta do what you gotta do.
indeed. I booked the title for another smell :)
Math.floor
is not the same as~~
-Math.trunc
is the equivalentHow to Find the Stinky Parts of Your Code ? shiva vashikaran mantra for love