Есть ли подсказка компилятора для GCC, чтобы заставить прогнозирование ветвей всегда идти определенным образом?


для архитектур Intel есть ли способ поручить компилятору GCC генерировать код, который всегда заставляет предсказание ветвей определенным образом в моем коде? Поддерживает ли это оборудование Intel? Как насчет других компиляторов или аппаратных средств?

Я бы использовал это в коде C++, где я знаю случай, когда я хочу работать быстро и не заботиться о замедлении, когда другая ветвь должна быть принята, даже если она недавно приняла это отделение.

for (;;) {
  if (normal) { // How to tell compiler to always branch predict true value?
    doSomethingNormal();
  } else {
    exceptionalCase();
  }
}

как следует из вопроса для Евджана Мустафы, может ли подсказка просто указать подсказку в первый раз, когда процессор встречает инструкцию, все последующие предсказания ветвей, функционирующие нормально?

7 105

7 ответов:

правильный способ определения вероятных / маловероятных макросов в C++11 заключается в следующем:

#define LIKELY(condition) __builtin_expect(static_cast<bool>(condition), 1)
#define UNLIKELY(condition) __builtin_expect(static_cast<bool>(condition), 0)

когда эти макросы определены таким образом:

#define LIKELY(condition) __builtin_expect(!!(condition), 1)

это может изменить значение if операторы и ломают код. Рассмотрим следующий код:

#include <iostream>

struct A
{
    explicit operator bool() const { return true; }
    operator int() const { return 0; }
};

#define LIKELY(condition) __builtin_expect((condition), 1)

int main() {
    A a;
    if(a)
        std::cout << "if(a) is true\n";
    if(LIKELY(a))
        std::cout << "if(LIKELY(a)) is true\n";
    else
        std::cout << "if(LIKELY(a)) is false\n";
}

и на его выходе:

if(a) is true
if(LIKELY(a)) is false

как вы можете видеть, определение вероятного использования !! как приведение к bool ломает семантику if.

дело тут не в этом operator int() и operator bool() должно быть связано. Что является хорошей практикой.

а что с помощью !!(x) вместо static_cast<bool>(x) теряет контекст для C++11 контекстные преобразования.

GCC поддерживает функцию __builtin_expect(long exp, long c) для обеспечения такого рода функция. Вы можете проверить документацию здесь.

здесь exp используется условие и c - это ожидаемое значение. Например, в вашем случае вы хотели бы

if (__builtin_expect(normal, 1))

из-за неудобного синтаксиса это обычно используется при определении двух пользовательских макросов, таких как

#define likely(x)    __builtin_expect (!!(x), 1)
#define unlikely(x)  __builtin_expect (!!(x), 0)

просто, чтобы облегчить задачу.

ум что:

  1. это не стандарт
  2. предсказатель ветви компилятора/процессора, вероятно, более квалифицирован, чем вы, в решении таких вещей, поэтому это может быть преждевременная микро-оптимизация

gcc имеет long _ _ builtin _ expect (long exp, long c) (выделено мной):

вы можете использовать __строение_готовы предоставить компилятору филиал прогнозная информация. В общем, вы должны предпочесть использовать фактические профиль обратной связи для этого (- fprofile-дуги), как и программисты заведомо плохо предсказывает, как их программы На самом деле работают. Тем не менее, есть приложения, в которых эти данные трудно получить собирать.

возвращаемое значение-это значение exp, которое должно быть целым выражение. Семантика встроенного заключается в том, что ожидается, что exp = = c. например:

if (__builtin_expect (x, 0))
   foo ();

указывает, что мы не ожидаем вызова foo, так как мы ожидаем, что x будет нуль. Поскольку вы ограничены интегральными выражениями для exp, вы следует использовать такие конструкции, как

if (__builtin_expect (ptr != NULL, 1))
   foo (*ptr);

при тестировании указателя или плавающей запятой ценности.

как отмечает документация, вы должны предпочесть использовать фактическую обратную связь профиля и эта статья показывает практический пример этого и как это в их случае, по крайней мере, заканчивается улучшение по сравнению с использованием __builtin_expect. Также смотрите как использовать оптимизацию профиля в g++?.

мы также можем найти статья новичков ядра Linux о макросах ядра likely() и unlikely(), которые используют этот особенность:

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

Примечание !! используется в макросе мы можем найти объяснение этому в зачем использовать !!(условие) вместо (условия)?.

просто потому, что этот метод используется в ядре Linux не означает, что всегда имеет смысл его использовать. Мы можем видеть из этого вопроса я недавно ответил разница между производительностью функции при передаче параметра в качестве константы времени компиляции или переменной что много рука прокатилась методы оптимизации не работают в общем случае. Нам нужно тщательно профилировать код, чтобы понять, эффективна ли техника. Многие старые методы могут даже не иметь отношения к современной оптимизации компилятора.

обратите внимание, хотя встроенные файлы не являются портативными лязгом также поддерживает __строение_ожидаем.

также на некоторых архитектуры это может не иметь значения.

нет, нет. (По крайней мере на современных процессорах x86.)

__builtin_expect упомянутый в других ответах влияет на то, как gcc организует ассемблерный код. это не напрямую влияние на предсказатель ветвей процессора. конечно, будут косвенные эффекты на предсказание ветвей, вызванные переупорядочением кода. Но на современных процессорах x86 нет инструкции, которая говорит процессору " предположим, что эта ветвь есть/нет взятый."

см. этот вопрос для более подробной информации:корпорация Intel х86 0x2E/0x3E префикс предсказание ветвлений на самом деле используется?

чтобы было понятно, __builtin_expect и/или -fprofile-arcsможете повышение производительности кода, как путем предоставления подсказок к предсказателю ветви через макет кода (см. оптимизация производительности x86-64 assembly-Alignment and branch prediction), а также улучшение поведения кэша, сохраняя" маловероятный " код из" вероятного " кода.

как и другие ответы уже все адекватно предложил, Вы можете использовать __builtin_expect чтобы дать компилятору подсказку о том, как организовать код сборки. Как официальные документы обратите внимание, что в большинстве случаев ассемблер, встроенный в ваш мозг, будет не так хорош, как тот, который был создан командой GCC. Всегда лучше использовать фактические данные профиля для оптимизации кода, а не угадывать.

по аналогичным линиям, но еще не упомянутым, является специфичным для GCC способом заставить компилятор для генерации кода по "холодному" пути. Это включает в себя использование noinline и cold атрибуты, которые делают именно то, что они звучат, как они делают. Эти атрибуты могут быть применены только к функциям, но в C++11 можно объявить встроенные лямбда-функции, и эти два атрибута также могут быть применены к лямбда-функциям.

хотя это все еще попадает в общую категорию микро-оптимизации, и поэтому применяется стандартный совет-тест не угадайте-я чувствую, что это больше вообще полезно, чем __builtin_expect. Вряд ли какие-либо поколения процессора x86 используют подсказки предсказания ветвей (ссылка), поэтому единственное, что вы все равно сможете повлиять, - это порядок кода сборки. Поскольку вы знаете, что такое код обработки ошибок или "крайний случай", вы можете использовать эту аннотацию, чтобы убедиться, что компилятор никогда не будет предсказывать ветвь к нему и свяжет его с "горячим" кодом при оптимизации по размеру.

образец использование:

void FooTheBar(void* pFoo)
{
    if (pFoo == nullptr)
    {
        // Oh no! A null pointer is an error, but maybe this is a public-facing
        // function, so we have to be prepared for anything. Yet, we don't want
        // the error-handling code to fill up the instruction cache, so we will
        // force it out-of-line and onto a "cold" path.
        [&]() __attribute__((noinline,cold)) {
            HandleError(...);
        }();
    }

    // Do normal stuff
    ⋮
}

еще лучше, GCC будет автоматически игнорировать это в пользу обратной связи профиля, когда она доступна (например, при компиляции с -fprofile-use).

смотрите официальную документацию здесь: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

__строение_может быть использован, чтобы сообщить компилятору, какой путь вы ожидаете ветке идти. Это может повлиять на то, как генерируется код. Типичные процессоры выполняют код быстрее последовательно. Так что если вы пишете

if (__builtin_expect (x == 0, 0)) ++count;
if (__builtin_expect (y == 0, 0)) ++count;
if (__builtin_expect (z == 0, 0)) ++count;

компилятор будет генерировать код типа

if (x == 0) goto if1;
back1: if (y == 0) goto if2;
back2: if (z == 0) goto if3;
back3: ;
...
if1: ++count; goto back1;
if2: ++count; goto back2;
if3: ++count; goto back3;

Если ваша подсказка верна, это будет выполнять код без каких-либо ветвей на самом деле выполняется. Он будет работать быстрее, чем обычная последовательность, где каждый оператор if будет ветвиться вокруг условного код и будет выполнять три ветви.

новые процессоры x86 имеют инструкции для ветвей, которые, как ожидается, будут приняты, или для ветвей, которые, как ожидается, не будут приняты (есть префикс инструкции; не уверен в деталях). Не уверен, что процессор использует это. Это не очень полезно, потому что предсказание ветвей будет справляться с этим просто отлично. Поэтому я не думаю, что вы действительно можете повлиять на ветку прогнозирование.

Что касается OP, нет, в GCC нет способа сказать процессору всегда предполагать, что ветвь занята или не занята. У вас есть __builtin_expect, который делает то, что говорят другие. Кроме того, я думаю, что вы не хотите сообщать процессору, взята ли ветка или нет всегда. Современные процессоры, такие как архитектура Intel, могут распознавать довольно сложные шаблоны и эффективно адаптироваться.

однако, есть моменты, которые вы хотите принять управления по умолчанию ветвь прогнозируется взятой или нет: когда вы знаете, что код будет называться "холодным" в отношении статистики ветвления.

один конкретный пример: код обработки исключений. По определению код управления будет происходить исключительно, но, возможно, когда это происходит, максимальная производительность желательна (может быть критическая ошибка, чтобы позаботиться как можно скорее), поэтому вы можете захотеть контролировать значение по умолчанию предсказание.

другой пример: вы можете классифицировать свои входные данные и перейти в код, который обрабатывает результат вашей классификации. Если существует много классификаций, процессор может собирать статистические данные, но терять их, потому что одна и та же классификация не происходит достаточно скоро, и ресурсы прогнозирования посвящены недавно вызванному коду. Я бы хотел, чтобы был примитив, чтобы сказать процессору "пожалуйста, не посвящайте ресурсы прогнозирования этому коду", как вы иногда можете скажите:"не кэшируйте это".