ядро языка — атрибуты и оптимизацияcore language — attributes and optimization

[[likely]] / [[unlikely]]

Атрибуты, размечающие ветку if/else или case как вероятную/маловероятную — стандартизированная замена __builtin_expect, подсказка компилятору для branch prediction, не меняющая семантику программы.Attributes marking an if/else branch or a case as likely/unlikely — a standardized replacement for __builtin_expect, a branch-prediction hint to the compiler that never changes program semantics.

чистая подсказка компилятору, не гарантияa pure compiler hint, not a guarantee неверная разметка может ухудшить производительностьa wrong hint can make performance worse

Что этоWhat it is

[[likely]] и [[unlikely]] — стандартные атрибуты C++20, размечающие ветку if/else (или case в switch) как вероятную или маловероятную к выполнению. Это подсказка компилятору для оптимизации порядка сгенерированного машинного кода (branch prediction hint) — стандартизация того, что раньше делалось через компиляторные интринсики вроде __builtin_expect в GCC/Clang или макросы-обёртки над ними.[[likely]] and [[unlikely]] are standard C++20 attributes marking an if/else branch (or a case in a switch) as likely or unlikely to execute. It's a hint to the compiler for optimizing the layout of the generated machine code (a branch prediction hint) — a standardization of what used to be done through compiler intrinsics like GCC/Clang's __builtin_expect, or macro wrappers around them.

до C++20 — непереносимый __builtin_expectbefore C++20 — the non-portable __builtin_expect
#define LIKELY(x)   __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
// работает в GCC/Clang, но не в MSVC —// works in GCC/Clang, but not in MSVC —
// нужен отдельный макрос-заглушка под каждый компилятор// a separate stub macro is needed per compiler

if (LIKELY(x > 0)) { fast_path(); }
else                    { slow_path(); }
C++20 — стандартный атрибутC++20 — a standard attribute
if (x > 0) [[likely]] {
    fast_path();
} else {
    slow_path();
}
// один синтаксис, переносим между всеми компиляторами C++20// a single syntax, portable across all C++20 compilers

Зачем ввелиWhy it was introduced

01

__builtin_expect и аналоги были непереносимы между компиляторами__builtin_expect and its equivalents weren't portable across compilers

GCC и Clang использовали одно и то же API __builtin_expect, но MSVC не имел прямого эквивалента (или предлагал другой механизм) — переносимый код был вынужден заворачивать это в собственные макросы с #ifdef под каждый компилятор, что само по себе было дополнительным источником ошибок и поддерживаемой сложности.GCC and Clang shared the same __builtin_expect API, but MSVC had no direct equivalent (or offered a different mechanism) — portable code had to wrap this in its own macros with per-compiler #ifdefs, which was itself an extra source of bugs and maintenance overhead.

02

Стандартные атрибуты дают единый переносимый синтаксисStandard attributes give a single, portable syntax

[[likely]]/[[unlikely]] — часть стандарта языка, а не расширение конкретного компилятора: один и тот же код компилируется на GCC, Clang и MSVC без макросов-обёрток и #ifdef. Компилятор, не поддерживающий или игнорирующий эти атрибуты, просто не выполнит связанную с ними оптимизацию — код остаётся корректным в любом случае.[[likely]]/[[unlikely]] are part of the language standard, not a specific compiler's extension: the same code compiles on GCC, Clang, and MSVC with no wrapper macros or #ifdefs. A compiler that doesn't support or simply ignores these attributes just skips the associated optimization — the code stays correct either way.

03

Атрибут применяется к ветке кода, а не к отдельному выражениюThe attribute applies to a branch of code, not a standalone expression

__builtin_expect оборачивал одно выражение (обычно условие if), тогда как [[likely]]/[[unlikely]] размечают саму ветку целиком — это более естественное место для подсказки, синтаксически ближе к тому, что программист реально имеет в виду ("эта ветка выполняется часто"), а не к переформулировке условия через тернарную арифметику.__builtin_expect wrapped a single expression (usually the if condition), while [[likely]]/[[unlikely]] mark the branch itself — a more natural place for the hint, syntactically closer to what the programmer actually means ("this branch runs often") rather than a condition reformulated through ternary arithmetic.

Как это работает под капотомHow it works under the hood

Атрибут влияет на layout сгенерированного машинного кода: компилятор обычно размещает более вероятную ветку "прямо по течению" инструкций, без дополнительного перехода (jump), а маловероятную — за пределами основного потока выполнения (например, в конце функции). Это дружелюбнее к аппаратному branch predictor'у и к prefetcher'у процессора, поскольку основной, вероятный путь исполнения не требует спекулятивного перехода. Ключевое: это ЧИСТО оптимизационная подсказка — семантика программы (результат выполнения) не меняется ни при использовании атрибута, ни при его полном игнорировании компилятором.The attribute affects the layout of the generated machine code: the compiler typically places the more likely branch "downstream" in instruction order, with no extra jump, and the unlikely branch out of the main execution flow (say, at the end of the function). This is friendlier to the CPU's hardware branch predictor and prefetcher, since the main, likely execution path requires no speculative jump. Crucially: this is a PURELY optimization hint — the program's semantics (its result) don't change whether the attribute is used or completely ignored by the compiler.

int process(int x) {
    if (x > 0) [[likely]] {
        return fast_path(x);      // компилятор кладёт этот путь без лишнего jump// the compiler places this path with no extra jump
    } else {
        return slow_path(x);      // этот путь может уехать в конец функции// this path may be relocated to the end of the function
    }
}

switch (error_code) {
    case 0: [[likely]]
        return ok();
    case 1: [[unlikely]]
        return handle_rare_error();
    default:
        return handle_unknown();
}

Атрибут ставится не только на if/else, но и на конкретную case-метку switch — сразу после двоеточия метки, до первого statement этой ветки. Это позволяет разметить, скажем, доминирующий тип записи в парсере логов, где остальные типы — редкие исключения:The attribute goes not only on if/else but also on a specific switch case label — right after the label's colon, before that branch's first statement. This lets you mark, say, the dominant record type in a log parser, where the other types are rare exceptions:

enum class LogKind { access, error, debug, trace };

void handle_line(LogKind kind, std::string_view line) {
    switch (kind) {
        case LogKind::access: [[likely]]     // 99% строк лога — access-записи// 99% of log lines are access records
            parse_access(line);
            break;
        case LogKind::error: [[unlikely]]
            parse_error(line);
            break;
        case LogKind::debug:
        case LogKind::trace: [[unlikely]]
            parse_debug(line);
            break;
    }
}

Примеры из реального кодаReal-world examples

типичные сценарии использованияtypical usage scenarios
01

Горячий цикл обработки событий с доминирующим типомA hot event-processing loop with a dominant type

Парсер потока событий, где подавляющее большинство сообщений — один и тот же тип (например, heartbeat или market data update) — реальный кандидат для likely/unlikely, поскольку дисбаланс сильный и путь горячий.A stream parser where the overwhelming majority of messages are the same type (say, a heartbeat or a market-data update) — a real candidate for likely/unlikely, since the imbalance is strong and the path is hot.

void on_event(const Event& ev) {
    if (ev.type == EventType::heartbeat) [[likely]] {
        update_last_seen(ev);
    } else {
        dispatch_rare_event(ev);
    }
}
02

Разметка error-ветки как unlikely при проверке кода возвратаMarking the error branch as unlikely in a return-code check

Классический паттерн системного кода — проверка результата системного вызова, где путь ошибки почти никогда не выполняется в нормальной работе.A classic systems-code pattern — checking a syscall's result, where the error path almost never executes during normal operation.

int ret = ::write(fd, buf, len);
if (ret < 0) [[unlikely]] {
    handle_write_error(errno);
    return false;
}
return true;
03

switch над типом сообщения сетевого протокола с [[likely]] на частом типеA switch over a network-protocol message type with [[likely]] on the common one

Протокол, где один тип сообщения (например, данные) составляет подавляющий трафик по сравнению с управляющими сообщениями.A protocol where one message type (say, data) accounts for the overwhelming share of traffic compared to control messages.

switch (msg.type) {
    case MsgType::data: [[likely]]
        process_data(msg);
        break;
    case MsgType::ack:
        process_ack(msg);
        break;
    case MsgType::control: [[unlikely]]
        process_control(msg);
        break;
}
04

Предостерегающий пример — неправильная разметка редкой ветки как likelyA cautionary example — wrongly marking a rare branch as likely

Здесь ошибка (не отказ диска, а конкретно ситуация "диск заполнен") встречается в 1 случае из 100000, но помечена как вероятная — компилятор располагает код так, будто это основной путь, что мешает предсказанию для реального горячего пути.Here the error (not a disk failure, but specifically "disk is full") occurs in 1 case out of 100,000, yet is marked as likely — the compiler lays out the code as if it were the main path, which hurts prediction for the actual hot path.

int ret = ::write(fd, buf, len);
if (ret < 0) [[likely]] {          // неверно! эта ветка редкая, не частая// wrong! this branch is rare, not common
    handle_write_error(errno);
    return false;
}
return true;
// компилятор кладёт error-путь "по течению" инструкций, а частый успешный —// the compiler places the error path "downstream" in instruction order, and the frequent success path —
// в сторону, ухудшая branch prediction относительно кода вообще без атрибута// off to the side, making branch prediction worse than code with no attribute at all

ГраблиGotchas

Это подсказка, а не гарантия — компилятор вправе её проигнорироватьIt's a hint, not a guarantee — the compiler is free to ignore it

Как и все compiler hints, [[likely]]/[[unlikely]] не обязывают компилятор ничего — он вправе полностью проигнорировать атрибут, если у него есть основания (например, собственный анализ профиля выполнения, противоречащий разметке). Полагаться на атрибут как на способ гарантированно повлиять на генерацию кода — ошибка: это совет, который может быть не принят.Like all compiler hints, [[likely]]/[[unlikely]] impose no obligation on the compiler — it's free to ignore the attribute entirely if it has reason to (say, its own profiling analysis contradicts the annotation). Relying on the attribute as a way to guaranteedly influence code generation is a mistake: it's advice that may go unheeded.

Неверная разметка может ухудшить производительность относительно отсутствия аннотацииA wrong annotation can make performance worse than having no annotation at all

Пометить редкую ветку как [[likely]] — не нейтральная ошибка, а активно вредная: современные компиляторы и без подсказок неплохо предсказывают вероятные ветки эвристически (по структуре кода, по наличию вызовов вроде throw или abort в одной из веток), и неверная явная подсказка перебивает эту эвристику, ухудшая branch prediction относительно кода вообще без атрибутов.Marking a rare branch as [[likely]] isn't a neutral mistake — it's actively harmful: modern compilers already predict likely branches reasonably well through heuristics even without hints (from code structure, from calls like throw or abort appearing in one branch), and an incorrect explicit hint overrides that heuristic, making branch prediction worse than code with no attributes at all.

Эффект заметен только в горячих циклах с реальным дисбалансом — не используйте "на всякий случай"The effect is noticeable only in hot loops with a real imbalance — don't use it "just in case"

Эффект от [[likely]]/[[unlikely]] обычно заметен только в горячих циклах с реальным сильным дисбалансом вероятностей веток (например, обработка ошибок, которая срабатывает в 1 случае из 100000). Расстановка атрибутов по всему коду без профилирования — типичный анти-паттерн preliminary optimization: усилия тратятся там, где эффекта либо нет, либо он отрицательный, а реальные горячие точки программы остаются неизвестны без измерений.The effect of [[likely]]/[[unlikely]] is usually noticeable only in hot loops with a genuinely strong imbalance in branch probabilities (say, error handling that fires once in 100,000 cases). Sprinkling the attributes across a codebase without profiling is a classic premature-optimization anti-pattern: effort gets spent where the effect is either absent or negative, while the program's actual hot spots remain unknown without measurement.