Question
likely and unlikely Macros in C: How Branch Prediction Hints Work
Question
In Linux kernel code, it is common to see conditions written like this:
if (unlikely(fd < 0))
{
/* Do something */
}
or:
if (likely(!err))
{
/* Do something */
}
These macros are typically defined as:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
I understand that they are used for optimization, but how do they actually work? What benefit do they provide, and how much improvement in performance or code size can usually be expected? Also, are they worth using in user-space programs, especially in performance-critical code, even though they reduce portability?
Short Answer
By the end of this page, you will understand what likely() and unlikely() mean in C, how they use __builtin_expect() to give the compiler branch prediction hints, and why that can affect generated machine code. You will also learn when these hints are useful, when they are unnecessary, and how real projects use them carefully rather than everywhere.
Concept
likely() and unlikely() are macros used to tell the compiler which outcome of a condition is expected to happen most of the time.
In GCC- and Clang-style C code, they usually wrap __builtin_expect():
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
Here is the key idea:
likely(x)means: "this expression is usually true"unlikely(x)means: "this expression is usually false"
What __builtin_expect() does
__builtin_expect(actual_value, expected_value) returns actual_value, but also gives the compiler a hint about which value is more probable.
So this:
if (unlikely(fd < 0))
becomes a hint saying:
- the expression
fd < 0is usually false - the error path is rare
Likewise:
Mental Model
Think of a branch like a hallway with two doors:
- one door is used 99% of the time
- the other door is only for emergencies
likely() and unlikely() are like putting a sign on the hallway for the architect:
- "Most people go through this door"
- "The emergency door is rarely used"
The sign does not force anyone to choose a door. Instead, it helps the architect design the building so the normal route is smooth and direct, while the rare route can be off to the side.
That is what the compiler does with these macros: it tries to make the common path cheaper and keep uncommon paths out of the way.
Syntax and Examples
The common Linux-style definitions are:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
Basic syntax
if (likely(count > 0)) {
process_items();
} else {
handle_empty_case();
}
This tells the compiler that count > 0 is usually true.
Example: rare error path
#include <stdio.h>
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
int divide(int a, int b) {
if (unlikely(b == 0)) {
return 0; // error case
}
return a / b;
}
int main(void) {
printf("%d\n", divide(, ));
;
}
Step by Step Execution
Consider this example:
#include <stdio.h>
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
int read_value(int ok) {
if (unlikely(!ok)) {
return -1;
}
return 42;
}
Step-by-step for read_value(1)
okis1!okbecomes0!!(!ok)is still0__builtin_expect(0, 0)returns0- The
ifcondition is false - The function returns
42
Step-by-step for read_value(0)
Real World Use Cases
likely() and unlikely() are most useful in places where one branch is strongly dominant.
Common real scenarios
Error handling in hot code
if (unlikely(ptr == NULL)) {
return ERROR;
}
In many systems, failure is rare, so the normal path should stay fast.
Fast-path vs slow-path logic
if (likely(cache_hit)) {
return cached_value;
}
return recompute_value();
If cache hits are common, it makes sense to optimize that path.
Kernel and systems programming
Operating systems, device drivers, schedulers, and memory allocators often have:
- very hot code paths
- tight loops
- rare exceptional conditions
This is where branch hints are more likely to matter.
Network packet processing
if (unlikely(packet == NULL)) {
drop_packet();
}
When millions of packets are processed, even small layout improvements can matter.
Input validation with rare failure
Real Codebase Usage
In real projects, developers usually do not wrap every if with likely() or unlikely().
They apply these hints selectively.
Common patterns
Guard clauses for rare failures
int process(struct item *item) {
if (unlikely(item == NULL)) {
return -1;
}
if (unlikely(item->data == NULL)) {
return -2;
}
return do_work(item);
}
This keeps the common valid path clean.
Fast path / slow path separation
if (likely(state == READY)) {
run_fast_path();
} else {
run_slow_path();
}
Validation in performance-sensitive functions
if (unlikely(size > limit)) {
log_error();
return 0;
}
Error handling macros and helpers
Common Mistakes
1. Thinking they change logic
These macros do not change what the program does.
Broken assumption:
if (unlikely(x > 0)) {
// some developers think this is treated differently logically
}
Reality:
- the condition is still evaluated normally
- true is still true, false is still false
- only optimization metadata changes
2. Using them everywhere
Overusing branch hints can make code noisy and harder to read.
Poor style:
if (likely(a > b)) {
...
}
if (likely(i < n)) {
...
}
if (unlikely(flag)) {
...
}
If every branch is annotated, the hints lose meaning and readability suffers.
3. Guessing the wrong branch probability
If you mark the uncommon path as likely, you can hurt performance.
if (likely(error_occurred)) {
handle_error();
}
If errors are actually rare, this hint is misleading.
4. Ignoring portability
__builtin_expect() is compiler-specific.
Safer pattern:
Comparisons
| Concept | What it does | Changes logic? | Typical use |
|---|---|---|---|
Plain if | Normal branch | No | Default choice |
likely(x) | Hints that x is usually true | No | Common fast path |
unlikely(x) | Hints that x is usually false | No | Rare error path |
| Profile-guided optimization (PGO) | Uses real runtime data | No | Whole-program optimization |
likely/unlikely vs plain if
Cheat Sheet
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
Quick rules
likely(x):xis usually trueunlikely(x):xis usually false- they do not change correctness
- they are compiler hints, not guarantees
- they are most useful in hot code with strongly biased branches
Why !!(x)?
- converts any nonzero value to
1 - converts zero to
0
Best uses
- rare error handling
- hot fast-path code
- low-level systems code
- branches confirmed by profiling
Avoid when
- code is not performance-sensitive
- branch probabilities are unclear
- readability would get worse
- you expect big gains without measuring
Portable fallback
#if defined(__GNUC__) || defined(__clang__)
FAQ
What do likely() and unlikely() do in C?
They wrap __builtin_expect() to tell the compiler which branch is expected to happen most often.
Do likely() and unlikely() make code faster?
Sometimes, but usually only by a small amount. They help most in hot code where one branch is clearly much more common than the other.
Do these macros affect program correctness?
No. They do not change the meaning of the condition. They only provide optimization hints.
Why does the Linux kernel use them so much?
The kernel contains performance-sensitive low-level code where error paths are often rare and fast paths are very important.
Should I use likely() and unlikely() in user-space code?
Only if the code is performance-critical and profiling or domain knowledge shows a strong branch bias. Otherwise, plain if is usually better.
Are likely() and unlikely() portable?
Not fully. They rely on compiler support for __builtin_expect(). Many projects add fallback macros for unsupported compilers.
How much performance improvement should I expect?
Mini Project
Description
Build a small C program that processes integer inputs and treats invalid values as rare errors. The project demonstrates how unlikely() can be used to mark uncommon validation failures while keeping the normal processing path straightforward.
Goal
Create a program that validates numbers, processes valid input, and uses unlikely() only on the rare error path.
Requirements
Requirement 1 Requirement 2 Requirement 3
Keep learning
Related questions
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C Pointer to Array vs Array of Pointers: How to Read Complex Declarations
Learn the difference between pointer-to-array and array-of-pointers in C, plus a simple rule for reading complex declarations correctly.