Разбираем макросы likely и unlikely из ядра Linux: как они влияют на производительность, ветвление и оптимизацию кода на C. Практические примеры и советы.
В исходном коде ядра Linux часто встречаются конструкции с макросами likely и unlikely. Они выглядят как обычные условия, но на самом деле дают компилятору подсказку о том, какая ветка кода выполняется чаще. Это позволяет оптимизировать код и повысить производительность. В этой статье мы разберём, как они работают, и как вы можете применить этот приём в своих проектах.
Макрос unlikely определён в ядре Linux следующим образом:
#define unlikely(e) __builtin_expect(!!(e), 0)Он передаёт выражение e в специальную встроенную функцию компилятора __builtin_expect вместе с ожидаемым значением 0. Аналогично likely передаёт ожидаемое значение 1. Эта функция сообщает компилятору, какое значение выражения наиболее вероятно. Компилятор использует эту информацию для оптимизации порядка инструкций в машинном коде, чтобы улучшить предсказание ветвлений процессором.
Рассмотрим два практически одинаковых цикла, отличающихся только использованием макросов likely и unlikely:
long unlikely_simple(long num_runs) {
long total = 0;
for (long i = 0; i < num_runs; i++) {
if (unlikely(i % 1000 == 0)) {
total += 2;
} else {
total += i + 1;
}
}
return total;
}
long likely_simple(long num_runs) {
long total = 0;
for (long i = 0; i < num_runs; i++) {
if (likely(i % 1000 == 0)) {
total += 2;
} else {
total += i + 1;
}
}
return total;
}В этих циклах первая ветка (if) выполняется в 0,1% случаев, а вторая (else) — в 99,9%. Мы можем изменить процент, варьируя делитель. Замеры времени выполнения для разных процентов показывают, что likely даёт выигрыш, когда предсказание совпадает с реальностью, но проигрывает, если реальность противоположна.
Таблица производительности (в микросекундах) для двух функций при разной вероятности попадания в первую ветку:
Видно, что при уменьшении вероятности первой ветки likely_simple деградирует, потому что мы подсказали компилятору оптимизировать под редкую ветку, а реальное выполнение идёт часто по другой.
Чтобы понять причину, посмотрим на сгенерированный ассемблерный код для обеих функций (компиляция с флагом -O2). Ключевое различие — порядок инструкций: в likely ветка if размещена сразу после проверки условия, а в unlikely — наоборот, сначала идёт else. Это связано с конвейеризацией процессора: он предзагружает инструкции, следующие по порядку в памяти. Если предзагруженная ветка не выполняется, конвейер сбрасывается, и происходит потеря производительности.
Макросы likely и unlikely полезны в горячих путях выполнения, где ветвление происходит миллионы раз. В обычном прикладном коде их эффект может быть незаметен, но в низкоуровневых системах, драйверах или высоконагруженных сервисах они способны дать ощутимый прирост. Используйте их осознанно: только когда вы точно знаете, какая ветка выполняется чаще. Неправильная подсказка может ухудшить производительность. Экспериментируйте и профилируйте свой код.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →