Bitwise Operations Performance: When They Actually Beat Arithmetic
There is a myth that bitwise operations are always faster. People write x << 1 instead of x * 2 thinking they are outsmarting the compiler. They are not. With -O2, GCC and Clang compile both to the exact same instruction.
But there are cases where bitwise operations genuinely win. This article covers when they do, when they do not, and how to check for yourself.
Compiler Optimizations: The Big Equalizer
Modern compilers perform strength reduction: they automatically replace expensive operations with cheaper equivalents. Multiplication by a power of two becomes a left shift. Division by a power of two becomes a right shift. You do not need to do this manually.
| Your Code | Compiled To (x86-64, -O2) | Verdict |
|---|---|---|
x * 2 | add eax, eax | Same as x << 1 |
x * 8 | lea eax, [0 + rax*8] | Same as x << 3 |
x / 2 (unsigned) | shr eax, 1 | Same as x >> 1 |
x % 16 (unsigned) | and eax, 15 | Same as x & 15 |
x % 10 | idiv instruction (slow) | Cannot optimize — not power of 2 |
Check it yourself with Compiler Explorer (godbolt.org). Write the C, set -O2, and compare the assembly.
Where Bitwise Wins: The Real Cases
1. Modulo With Power of Two (When the Compiler Cannot Prove It)
If the divisor is a compile-time constant power of two, the compiler handles it. But if it is a runtime variable, the compiler cannot optimize — and integer division is one of the slowest CPU instructions.
// Slow — runtime division (20-80 cycles on modern CPUs)
int index = hash % table_size;
// Fast — bitwise AND, but only if table_size is a power of 2
// (1 cycle on modern CPUs)
int index = hash & (table_size - 1);
// This is why hash tables use power-of-two sizes.
2. Packing Multiple Values Into One Integer
Memory is the bottleneck, not CPU. Packing RGBA color channels or multiple flags into a single integer avoids memory access and cache misses.
// RGBA packing: 4 channels in one uint32_t (saves 12 bytes per pixel)
uint32_t rgba = (r << 24) | (g << 16) | (b << 8) | a;
// Unpacking
uint8_t r = (rgba >> 24) & 0xFF;
3. Branchless Code With Bit Tricks
Branch misprediction costs 10-20 cycles. Bitwise operations let you avoid branches entirely.
// Branch (slow if unpredictable)
int abs_val = (x < 0) ? -x : x;
// Branchless (faster when x is random positive/negative)
int abs_val = (x ^ (x >> 31)) - (x >> 31);
// Branch: min of two values
int min_val = (a < b) ? a : b;
// Branchless: min
int min_val = b ^ ((a ^ b) & -(a < b));
4. SIMD: Where Bitwise Operations Shine
SIMD (SSE/AVX/NEON) instructions operate on 128, 256, or 512 bits at once. Bitwise AND/OR/XOR are the cheapest SIMD operations — they are perfectly data-parallel with no dependencies between lanes.
// AVX2: AND two 256-bit vectors in one instruction
// __m256i result = _mm256_and_si256(a, b);
// This is 32 byte-sized ANDs, all happening simultaneously.
When NOT to Use Bitwise Tricks
The XOR swap trick (a ^= b; b ^= a; a ^= b) is slower than a temp variable on modern CPUs. The compiler recognizes the temp-variable swap and optimizes it. The XOR version confuses the compiler's alias analysis and prevents optimization.
Readability matters more than cycles. If x * 8 makes your intent clearer than x << 3, use multiplication. The compiler will fix it. Only reach for bitwise operations when you have measured that it matters — or when the operation is inherently bitwise, like bitmask manipulation.