Edit: you obviously have to multiply by 8 to convert bytes to bits
You are definitely correct on most modern architectures which have a single operation count leading/trailing zeros that are typically no more expensive than an integer multiply. This has been latency = 3, throughput = 1 for most recent Intel arch.
I think a redo assuming modern architectures is long overdue; i.e. let's work off the basis that count leading/trailing zeros, PDEP/PEXT, etc are fast and cheap and start from there.
Bonus round: if you had a lot of these operations to do, then you still might be able to go faster on x86 on SSE2 or AVX2 by using this trick, as the penalty to pass a SIMD register full of integers to a floating point op or vice versa is just one cycle. This trick becomes obsolete yet again with AVX512, which introduces parallel count leading zeros.
Also multiply adds can be fused so if you're doing that it cab be faster to just multiply by a different number instead of bit twiddling.
I'm not sure which bit hack you're talking about that's done with a multiply or multiply-add. There's a nice use involving De Bruijin sequences for doing lg2 of a single bit that's very instructive - is that what you meant?
When you first learn about 'cool hacks' like these, you'll want to use everywhere.
Then you'll realize that they don't apply to certain data types in certain environments.
Then you'll come to realize that the rules governing which hacks to use are too complex to remember.
Then you'll want to write a program that figures it out for you.
Then you'll realize that those programs already exist, and they are called compilers.
So now you are back where you started. You have made no progress, but you have gained an important insight.
There's a reason a lot of these potentially non-portable tricks show up in high-performance computing (particularly in videogames) and in the embedded space.
Otherwise, remember the first rule of code optimization for beginners: "Don't."
EDIT: And by "they don't apply", I mean they can crash your program or even silently corrupt your data.
For games, this can mean a higher frame rate. For data processing, it can mean the data gets processed significantly faster. It's especially important for things like video encoders, where even a 1% savings in CPU time can translate directly to 1% more potential revenue for the same operating cost at a company that relies heavily on video encoding.
Yeah, saving those cycles doesn't really mean anything to a beginner, but they can be a huge savings for high performance computation applications.
union { int i; float f;} u; u.i=*i;
Now u.f contains the same bit pattern as the integer pointed to by "i" but interpreted as a float.
Compilers know how to generate code efficiently out of this.
Iirc this is implementation defined behavior, e.g. things like endianness alignment or padding, but it's not undefined behavior, whose evil effects can bubble up in unexpected parts of the program that apparently don't even touch the bad cast.
float f;
memcpy(&f, i, sizeof f);
Don't worry about the implied overhead of the call to memcpy; the compiler will replace it with an intrinsic and emit the expected movss instruction.Back in my C++ days, I would much prefer pointer-cast to union-struct, because the latter was subject to alignment/padding issues you mention, while for the former, I know of no desktop architecture that could mess up a int* -> float* cast.
I'm also doubly surprised because being able to do that stuff is what I'd consider the selling point of C/C++ over higher-level languages.
Yeah, C and C++ aren't the language most people think they are, and the strict aliasing rules are the most clear manifestation of that.
The replacement is often "gcc with inline assembly" or "gcc with lots of __attribute__ extensions such as __may_alias__" or "gcc with optimizations disabled".
The need doesn't go away, despite the wishes of a language committee now dominated by compiler suppliers. From day 1, long before the language was standardized and even before the original (not ANSI) book was published, C was being used with aliasing. It was, after all, developed to support the creation of Unics (before that became UNIX).
> Accessing an object by means of any other lvalue expression (other than unsigned char) is undefined behavior
https://wiki.sei.cmu.edu/confluence/display/c/EXP39-C.+Do+no...
That said, memcpy and a compiler that understand the memcpy intrinsic is probably safer; that said I have a vague memory of hitting an embedded compiler where memcpy wasn't an intrinsic
https://graphics.stanford.edu/~seander/bithacks.html