Applying GCC’s __builtin_expect for Branch Prediction in Performance-Critical Code
When a branch in performance-critical C code runs hot, __builtin_expect tells GCC the likely path—but only use it for predictable branches and verify the impact with assembly or profiling.
02 Sept 2025, 09:43 UTC

Problem: Branch Overhead in Hot Loops
You maintain a high-throughput C server where a null-pointer check appears in a tight loop. Each iteration evaluates if (ptr != NULL) before dereferencing. The branch is almost always taken (ptr is valid), but the compiler has no information about that, so it emits a generic conditional jump. In a path executed millions of times per second, that extra indirection adds up. Can you guide the compiler to emit leaner code without changing program semantics?
Decision Guide: Using __builtin_expect
Constraints
- GCC version 2.5 or later; the built-in works on all supported architectures.
- The branch should have a stable runtime profile; frequent likelihood flips can degrade performance.
- The hint is non-binding; the compiler may ignore it, especially with aggressive optimization.
Supported Options
| Hint | Semantics | Typical Use Case |
|---|---|---|
__builtin_expect(expr, 1) | Evaluates to expr and tells the compiler the branch is likely | e.g. if (__builtin_expect(x != NULL, 1)) |
__builtin_expect(expr, 0) | Evaluates to expr and tells the compiler the branch is unlikely | e.g. if (__builtin_expect(err, 0)) |
Trade-offs
When the hint matches the actual runtime behavior, GCC can reorder instructions, eliminate redundant checks, or emit conditional moves instead of jumps. If the hint is wrong—for example, you mark a branch likely but it rarely takes that path the compiler may generate slower code than it would without the hint. Compilers with profile-guided optimization (-fprofile-use) can often learn the true profile better than a static hint. Use the built-in only when you have evidence the branch is predictable, and always measure after applying it.
Concrete Implementation
Code Example
if (__builtin_expect(ptr != NULL, 1) { use(ptr); } else { return NULL; }Assembly Verification
Compile with optimization and dump assembly to inspect the compiler’s response:
gcc -O2 -S my_code.c
In the generated .s file, look for a conditional jump preceded by a bt (bit test) or a jxx pattern near the branch. With the hint, you may see the compiler hoist the check or use a conditional move (cmov) on supported architectures. If the hint is omitted or ignored, the code will follow the standard test + jxx sequence.
Profiling Verification
Use a wall-clock profiler to compare execution time with and without the hint:
gcc -O2 -fno-inline-functions -o my_code_without my_code.c gcc -O2 -o my_code_with my_code.c perf stat -r 5 ./my_code_with perf stat -r 5 ./my_code_withoutIf the hinted binary shows a consistent reduction in cycles per iteration (typically 1-5% for very hot loops), the hint is beneficial. Repeat with -fprofile-use after a preliminary run with -fprofile-generate and -fprofile-use; the compiler’s dynamic profile often supersedes the static hint.
Limitations & When Not to Use
- Hint mismatch: Providing an incorrect likelihood can degrade performance. Profile before and after applying the built-in.
- Compiler ignores hint: With -O3, -flto, or aggressive inlining, the compiler may discard the hint entirely.
- Unstable branches: If the branch probability changes with input workload, the hint can do more harm than good.
- Portability: While the built-in is defined by the GCC language model and works across supported architectures, it is not part of the C/C++ standard. Code using it remains portable across GCC-based toolchains but may not translate directly to other compilers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.