Подсказка для прогноза ветвления в утверждениях

У меня есть пользовательский макрос ASSERT(...), который я использую в приложении С++.

#include <stdlib.h>
#include <iostream>

/// ASSERT(expr) checks if expr is true.  If not, error details are logged
/// and the process is exited with a non-zero code.
#ifdef INCLUDE_ASSERTIONS
#define ASSERT(expr)                                                      \
    if (!(expr)) {                                                        \
        char buf[4096];                                                   \
        snprintf (buf, 4096, "Assertion failed in \"%s\", line %d\n%s\n", \
                 __FILE__, __LINE__, #expr);                              \
        std::cerr << buf;                                                 \
        ::abort();                                                        \
    }                                                                     \
    else // This 'else' exists to catch the user following semicolon
#else
#define ASSERT(expr)
#endif

Недавно я читал код модуля ядра Linux и сталкивался с наличием макросов likely(...) и unlikely(...). Они дают указание процессору, что данная ветка более вероятна, и что конвейер должен оптимизировать для этого пути.

Утверждения, по определению, должны оцениваться как истинные (т.е. likely).

Могу ли я предоставить аналогичный намек в моем макросе ASSERT? Какой здесь основной механизм?

Очевидно, я буду измерять любую разницу в производительности, но теоретически это должно иметь какое-то значение?

Я только запускаю свой код в Linux, но мне было бы интересно узнать, есть ли кросс-платформенный способ сделать это тоже. Я также использую gcc, но также хотел бы поддержать clang.

Ответ 1

Увеличение производительности вряд ли будет значительным, но именно так определяются макросы ядра Linux:

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

Итак, вы можете изменить свое условие следующим образом (предполагая, что expr ожидается как истина, и поэтому !(expr) ожидается false):

if (__builtin_expect(!(expr), 0)) {

Или вы можете определить те же макросы, что и ядро, и использовать их для лучшей читаемости.

Это gcc builtin, поэтому не переносится, конечно.

Это говорит о том, что clang также поддерживает встроенный. Кстати, вы можете использовать указанные выше макросы и условно определить их как #define likely(x) (x) для компиляторов, которые не поддерживают встроенный.

В вашем случае предсказание будет хорошим (либо это, либо вы прерываете), поэтому не должно быть риска пессимизации, но если вы решите использовать встроенный более широко, здесь слово рекомендации по документации gcc:

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

Ответ 2

Для многих процессоров likely и unlikely (или что-то еще в этом случае) не предоставляют подсказки ветки процессору (только для компилятора, который может использовать его для оптимизации по-разному, подобно профилю, управляемому профилем оптимизация) по той простой причине, что нет способа сделать это.

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

В ARM нет (еще?) есть подсказки ветвления. PPC, IA64 и SPARC имеют намеки на ветки, я не знаю, использует ли GCC likely и unlikely для них, но, по крайней мере, это может быть.

Ответ 3

Нет никакой дополнительной аннотации. Компилятор уже знает о abort, который вызывается очень редко (не более одного раза для выполнения программы), поэтому компилятор будет рассматривать ветвь, содержащую abort как маловероятную ветвь. Вы можете проверить это, посмотрев объявление abort. В glibc объявляется как

extern void abort (void) __THROW __attribute__ ((__noreturn__));

и в Visual Studio 2013:

_CRTIMP __declspec(noreturn) void __cdecl abort(void);