Extreme optimization of integer binary search
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Standard binary search runs in O(log n) time, but micro-optimizations can significantly reduce the constant factor. Techniques include branchless comparisons, avoiding integer overflow in midpoint calculation, prefetching cache lines, using Eytzinger layout for cache-friendly access, and converting to a branchless conditional move. These optimizations matter in performance-critical code like database indexes, competitive programming, and real-time systems.
Standard Binary Search
Optimization 1: Overflow-Safe Midpoint
The naive mid = (lo + hi) / 2 overflows when lo + hi exceeds INT_MAX:
In Java, the >>> operator performs unsigned right shift, which handles the overflow case correctly. This was a real bug in Java's Arrays.binarySearch that went undetected for years.
Optimization 2: Branchless Binary Search
Branch mispredictions are expensive on modern CPUs (10-20 cycle penalty). Binary search on random data has ~50% misprediction rate. A branchless version uses conditional moves (cmov):
The key insight is (arr[lo + half] < target) evaluates to 0 or 1, and multiplication replaces the branch. The compiler turns this into a cmov instruction.
Optimization 3: Eytzinger Layout
Standard binary search has poor cache behavior — it accesses memory positions that are far apart. The Eytzinger layout rearranges array elements into a breadth-first order of an implicit binary tree:
The Eytzinger layout stores the root at index 1, left child at 2k, and right child at 2k+1. This means successive comparisons access nearby memory addresses, improving cache hit rates by 2-3x for large arrays.
Optimization 4: Prefetching
Explicit prefetch hints tell the CPU to load cache lines before they are needed:
Prefetching is most effective for large arrays that do not fit in L2 cache (typically > 256KB of data).
Optimization 5: Compiler Hints and SIMD
For searching multiple targets simultaneously, SIMD instructions can process 4-16 comparisons in parallel using AVX2/AVX-512.
Benchmarking the Optimizations
Typical speedups over standard binary search for arrays of 10M integers:
| Technique | Relative Speed | Notes |
| Standard binary search | 1.0x (baseline) | Branch-heavy, cache-unfriendly |
| Overflow-safe midpoint | 1.0x | Same speed, just correctness |
| Branchless (cmov) | 1.5-2x | Eliminates branch mispredictions |
| Prefetching | 1.3-1.8x | Helps for large arrays (> L2 cache) |
| Eytzinger layout | 2-3x | Best cache behavior |
| Eytzinger + branchless | 3-5x | Combines both benefits |
Common Pitfalls
- Integer overflow in midpoint calculation:
(lo + hi) / 2overflows when both values are large. Always uselo + (hi - lo) / 2or unsigned arithmetic. This was a real bug in the JDK's binary search for nearly a decade. - Branchless code that the compiler undoes: Compilers may convert your branchless multiplication back into branches. Check the assembly output with
gcc -Sor Compiler Explorer (godbolt.org) to verifycmovinstructions are generated. - Eytzinger layout overhead for small arrays: The Eytzinger layout adds a preprocessing step (O(n) time and space) and complicates insertion. It only pays off for large, read-heavy arrays (thousands of elements or more) that are searched repeatedly.
- Prefetching too aggressively: Prefetching every iteration adds instruction overhead. Only prefetch when the array exceeds L2 cache size. Unnecessary prefetch instructions consume memory bandwidth and can slow down small-array searches.
- Micro-optimizing before profiling: These optimizations improve binary search by constant factors, not asymptotic complexity. If binary search is not the bottleneck (often the surrounding code dominates), these optimizations provide zero measurable improvement. Always profile first.
Summary
- Use
lo + (hi - lo) / 2to avoid integer overflow in midpoint calculation - Branchless binary search with conditional moves (
cmov) eliminates branch misprediction penalties - Eytzinger layout rearranges array elements for cache-friendly access (2-3x speedup on large arrays)
- Prefetching upcoming cache lines reduces memory latency for arrays larger than L2 cache
- Combine techniques (Eytzinger + branchless + prefetch) for maximum throughput (3-5x over standard)
- Profile before optimizing — these techniques only matter when binary search is the actual bottleneck

