Pull to refresh

Comments 67

А использовать битовые поля, не?

struct state {
        uint32_t        One: 1;
        uint32_t        Two: 1;
        uint32_t        Three: 1;
};

state State;
State.One = 1;
State.Two = 0;
У нас «прокачанные» перечисления с интроспекцией и прочими плюшками, так что не.
Выводы в статье правильные. А читаемость можно подлатать макросами.
Кроме того, компиляторы сейчас очень умные и можно не жадничать в плане переменных в ущерб читаемости и разбивать строку вычислений на отдельные читаемые строки. (Наоборот, приходится еще volatile ставить, чтобы компилятор не наоптимизировал даже с -o0).

В стареньких DSP иногда все печально с переходами. Например jmp только дальше по программе, без шагов назад, поэтому там подобный подход необходим для выживания.
А можно глупый вопрос? Как в этих стареньких DSP реализовывались циклы и вызовы подпрограмм?
Вопрос скрывает в себе ответ: никак. Если работа идет со звуком ( или каким-то датчиком, например, вибрации), то 48000 раз в секунду запускается одна и та же программа длиной 128 (1024, 4096) команд и всё. На старте — читаются данные с АЦП, на выходе — отправляются в ЦАП (ну, или иные интерфейсы).
Переход — только вперед. «Обработка» «массивов» — аппаратный кольцевой буфер, где можно в начало кольца дописать данные. Для хранения мгновенных данных (состояний ручек управления, или фильтров) — регистры.
Регистр флагов показывает состояние аккумулятора. Но чаще всего, логика реализуется математикой как в статье.
У нас «прокачанные» перечисления с интроспекцией и прочими плюшками, так что не.

А что вы используете для прокачки перечислений?

Макросы, куда же без них. Распространенный вариант — оборачивать определение перечисления в макрос, а вот тут используют __PRETTY_FUNCTION__ и __FUNCSIG__, чтобы вытащить имена перечислений, и даже оборачивать ничего не надо.
а вот тут используют __PRETTY_FUNCTION и FUNCSIG__, чтобы вытащить имена перечислений, и даже оборачивать ничего не надо.

Нет. Глянул и не смог понять как это могло бы работать. Уже думал, что я чего-то не знаю и вот оно открытие, но нет. Всё оказалось куда проще.


Расскажу как это работает, авось кому интересно будет.


База для реализации — это данный фокус. Смысл его заключается в том, что если мы передаём правильное значение енума, то он преобразуется в имя значения. Если нет — там остаётся каст.


Далее оно берёт и тупо перебирает какой-то диапазон значений, в поисках правильных. https://godbolt.org/z/8Q-EEf — как-то так.


Захардкодены туда -128...128 т.е. универсальность крайне сомнительна. Если значение выйдет за диапазон — оно никак про него не узнает. Опасно юзать такое. для чего-то шире uint8_t.

Нет.

Это все, конечно, так, но никак не противоречит тому, что я написал.

У этого решения есть еще два недостатка — первый это то, что в исполняемый файл попадают строки вида «constexpr auto f() [with auto v = Color::RED]» целиком, из-за чего бинарник разбухает. Из этого вытекает второй — имя дергается как subview от этой строки, и оно получается не нуль-терминировано.
Это все, конечно, так, но никак не противоречит тому, что я написал.

Там нету отсылки на то, что решение не полноценно. О чём я и сообщил. Т.е. оборачивать надо и ваши енумы туда попросту не засунуть. К тому же, я нигде не говорил, что что-то чему-то там противоречит. Я лишь сообщил, что упомянутое решение не соответствует описанию. Всё.


У этого решения есть еще два недостатка — первый это то, что в исполняемый файл попадают строки вида «constexpr auto f() [with auto v = Color::RED]» целиком, из-за чего бинарник разбухает. Из этого вытекает второй — имя дергается как subview от этой строки, и оно получается не нуль-терминировано.

Не вижу проблемы ни в первом ни во втором случае. В любом случае это просто детали реализации — https://godbolt.org/z/RzFTL5 — вот, никаких строк нет.

Вы процитировали мое сообщение и написали «Нет.», по-моему, это воспринимается довольно однозначно. И, кстати, об ограничениях решения там написано.

Вот, никаких строк нет.

Точно, компилятор оставлял те строки, т.к. мы view на них делали, если скопировать символы, то необходимость оставлять их пропадет.
Только я бы добавил еще одну функцию-прослойку, чтобы гарантировать constexpr контекст для вызова to_array.

template<auto v> constexpr auto get_name()
{
    constexpr auto name = to_array<v>();
    return name;
}
Вы процитировали мое сообщение и написали «Нет.», по-моему, это воспринимается довольно однозначно.

Ну да, ссылка не соответствует описанию.


И, кстати, об ограничениях решения там написано.

Ну да, в отдельной ссылке без какого-либо упоминания её в нужном контексте.


Точно, компилятор оставлял те строки, т.к. мы view на них делали, если скопировать символы, то необходимость оставлять их пропадет.

Если бы он их не убирал — там было бы минимум более 200 строк на каждый енум.


Только я бы добавил еще одну функцию-прослойку, чтобы гарантировать constexpr контекст для вызова to_array.

Это не публичный интерфейс.

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

Как оказалось, это не так — действительно, -cond дает нам именно то, что нужно: при cond = false это даст те же нули, однако при cond = true мы получим -1, то есть единицы во всех разрядах. Отталкиваясь от этого, видимо, можно и получить оба этих выражения.
Эти варианты еще хороши тем, что на всех компиляторах дают самый лучший результат (на MSVC asm аналогичен clang и gcc).
Clang 9.0 (последние два — варианты по ссылке):

gcc 9.2
Хех, я тоже хотел сам.
Но потом вспомнил, что я сегодня с похмелья, и полез на Bit Twiddling Hacks :)
Преобразование из bool (0 или 1) во «все нули или все единицы» гениально!
(x | y) & ~0 ≡ (x | y) & 1 ≡ x | y

Минус перед единичкой не забыли? А то эквивалентность не выходит.
Если добавить «для каждого бита», то нет :)
Нет, тут имеется в виду не С++ код, а логическое выражение, так что 1 это логическая единица. В этом случае '~' лучше поменять на '¬', да.

clang вставляет cmove только если у него нет PGO (веса бранчей) если в бенчмарке все время будет выбираться одно из условий и только — то никакого cmove не будет вставлено.

Сама идея использовать
void SetFlag(Flags& x, Flags y, bool cond)

вместо идеоматичных
flags |= flag1;
flags &= ~flag2;

плохая тем, что заставляет заранее пессимизировать код. Ведь `cond` в большинстве случаев константный — true или false, а компилятор не всегда сможет заинлайнить эту функцию (особенно, если используется не «наивный» вариант).
Так никто и не говорил об этом :) Эту функцию планировалось использовать в случае, если cond известно только в рантайме. Для приведенных выше строк предназначаются:
RaiseEnumFlag(state, flag);
ClearEnumFlag(state, flag);
Программисты народ ленивый, особенно если их много в команде и каждый пишет свои вспомогательные ф-ции ))
Выучив SetFlag, они могут на нём и остановиться.

Не удивлюсь, если в C++ можно как-то намутить 3 варианта SetFlag, отдельные для константных значений true и false у последнего параметра, по аналогии с частичной специализацией шаблона. Тогда думать не надо, что использовать — всегда SetFlag.
Да легко!
template<class T>
constexpr FlagWrapper<T> Flags(T& value)
{
  return value;
}

template<class T>
class FlagWrapper
{
  T& value;
public:
  FlagWrapper(T& value) : value(value){}

  template<T flag>
  FlagWrapper& Set() 
  {
    value |= flag; 
    return *this;
  }

  FlagWrapper& Set(T flag) 
  {
    value |= flag; 
    return *this;
  }

  template<int flag>
  FlagWrapper& SetAt() 
  {
    value |= T{1} << flag; 
    return *this;
  }

  FlagWrapper& SetAt(int flag) 
  {
    value |= T{1} << flag; 
    return *this;
  }

  template<T flag>
  FlagWrapper& Clear() 
  {
    value &=~ flag; 
    return *this;
  }

  FlagWrapper& Clear(T flag) 
  {
    value &=~ flag; 
    return *this;
  }

  template<T flag>
  FlagWrapper& Toggle() 
  {
    value ^= flag;
    return *this;
  }

  FlagWrapper& Toggle(T flag) 
  {
    value ^= flag; 
    return *this;
  }

}



Usage:
  int bitField;
  Flags(bitField).Set(0x42).SetAt(4);

Можно сделать оооооооочень много вариаций на тему. И ленивые, и константные вычисления, и немутабельные значения… Что угодно. И ещё прикрутить SFINAE, Concepts и прочие умные слова. На любой вкус.
Нет, это не то.
Надо чтобы
SetFlag(flags, bit, true);
SetFlag(flags, bit, false);
SetFlag(flags, bit, myboolean);

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

Есть ли в C++ статическая диспетчирезация по значению параметра?
1. Значит компилятор считает, что цена вызова ниже цены инлайнинга — так бывает.
2. template… — будет специализация. Если она используется в одном месте — значит скорее всего будет заинлайнена, если в нескольких и тяжёлая — см. п. 1.
2а. constexpr отчасти решает и эту проблему.
Есть ли в C++ статическая диспетчирезация по значению параметра?

По значению прямо параметра функции, наверное, не получится пока, во всяком случае, я не знаю, как. Была у меня идея сделать это дело как-нибудь через шаблонные типы с частичной специализацией, на основе которых потом делаются перегруженные функции, и conversion constructor'ы в этих специализированных типах для прозрачного преобразования из bool в них, но не заработало это нормально.

Можно сделать просто через отдельные типы вместо одного bool на все случаи жизни и перегрузку (или шаблонизацию со SFINAE, по вкусу), что-то типа такого:

Заголовок спойлера
namespace Action
{
    struct SetT {};
    struct ClearT {};

    constexpr SetT Set;
    constexpr ClearT Clear;

    struct Copy {
        explicit Copy(bool val) : m_val(val) {};

        bool m_val;
    };
};

void SetFlag(int f, int b, Action::SetT)
{
[...]
}

void SetFlag(int f, int b, Action::ClearT)
{
[...]
}

void SetFlag(int f, int b, Action::Copy v)
{
[...]
}

int main()
{
[...]
    SetFlag(f, b, Action::Set);
    SetFlag(f, b, Action::Clear);
    SetFlag(f, b, Action::Copy {v});
[...]
}



Но да, это не помешает написать что-нибудь типа

SetFlag(f, b, Action::Copy {true});

Но по крайней мере это будет странно выглядеть :)
Есть ли в C++ статическая диспетчирезация по значению параметра?
В C++ нету, в gcc/clang есть __builtin_constant_p.

Можете посмотреть как open(3) обрабатывается в GLibC — там как раз нужно смотреть на констранту, чтобы понять — нужен третий (опциональный) аргумент или двух достаточно…
open работает через vararg, дочитывая из стека необходимый аргумент.
Я не вижу тут, чтобы для разного значения параметра статически вызывались разные функции.

Разве что предположить __forceinline и выкидывание компилятором ненужной ветки, которая включается по значению параметра. Но так никакой __builtin_constant_p не нужен (для фиксированного кол-ва аргументов).
# define __OPEN_NEEDS_MODE(oflag) \
  (((oflag) & O_CREAT) != 0 || ((oflag) & __O_TMPFILE) == __O_TMPFILE)

__fortify_function int
open (const char *__path, int __oflag, ...)
{
  if (__va_arg_pack_len () > 1)
    __open_too_many_args ();

  if (__builtin_constant_p (__oflag))
    {
      if (__OPEN_NEEDS_MODE (__oflag) && __va_arg_pack_len () < 1)
        {
          __open_missing_mode ();
          return __open_2 (__path, __oflag);
        }
      return __open_alias (__path, __oflag, __va_arg_pack ());
    }

  if (__va_arg_pack_len () < 1)
    return __open_2 (__path, __oflag);

  return __open_alias (__path, __oflag, __va_arg_pack ());
}


Я не вижу тут, чтобы для разного значения параметра статически вызывались разные функции.
Вы не видите тут, что для некоторых значений вызывается __open_2, а для некоторых __open_alias? Серьёзно?

open работает через vararg, дочитывая из стека необходимый аргумент.
Только если он нужен.

P.S. В clang то же самое оформляется немного по другому. В общем если вам «ехать» — варианты есть. А шашечек (в смысле стандарта) — пока нет.
я вижу, что синтаксически
open("", O_RDONLY) и
open("", O_CREAT, 0644)

вызывают одну функцию open, которая описана выше. А она уже вызывает какие-то другие функции.

Тогда можно считать, что задача решается
__forceinline void SetFlag(Flags& x, Flags y, bool cond) {
    if (cond) x |= y; else x &= ~y;
}

надеясь, что компилятор уберёт if, если значение cond известно.
Как я уже сказал — ехать можно, шашечек нет. В случает с clangом функций может быть несколько, впрочем.
В случает с clangом функций может быть несколько, впрочем.
Не понял, как.

__overloadable позволяет создавать перегрузки ф-ции на разное число аргументов. Что в C++ уже есть в стандарте.

__clang_error_if может проверить параметр и выдать ошибку в compile-time

А вот как сделать, чтобы open("", O_RDONLY) и open("", O_CREAT) описывались разными ф-циями, а не if-ом внутри одной — для меня неясно.
Ну вот примерно как-то так:
inline int foo(int x)
  __attribute__((enable_if(x, "True case"))) {
    return 1;
}

inline int foo(int x)
  __attribute__((enable_if(!x, "False case"))) {
    return 2;
}

inline int foo(int x) {
  return 3;
}

Ну, тут проблема в том, что если значение cond неизвестно — то этот код не будет оптимальным.

Это и было требованием:
при cond=true — одна ф-ция, при cond=false — другая, при переменном cond — третья.
Практически __attribute__((always_inline)) плюс __builtin_constant_p это гарантируют.

Хотя решение clangа выглядит красивее, но проблема тут та же: вы можете сделать функцию, которае не принимает ничего кроме констант, но если вы сделаете функцию, которая может принимать и константы и неконстанты, то всегда есть шанс, что что-то, что вам кажется константой для компилятора таковой не окажется — и вызовется общий случай.
UFO just landed and posted this here
А зачем
) != 0) == false)
?

Явное лучше неявного?

Напомнило…
if ((a == b).ToString().Length == "False".Length) ...
if ((a == b).ToString().Length == "True".Length) ...

Нет.


Во-первых, в контекстах с выраженной булевской семаниткой, явное намного хуже неявного.


Во-вторых, почему результат предыдущего != 0 — неявен, в результат == false вдруг явен? Где логика?


И, самое главное, в третьих, если со скрежетом зубов еще можно пропустить == true, то == false — это совершенно нечитаемо. Скрытая в == false неявная инверсия полностью убивает читаемость кода.

Тоже забавная вариация на тему:
switch (boolValue)
{
case false: //
  break;
case true: //
  break;
default: //
  break;
}
Провести проверку профпригодности?
Из которой можно узнать много нового.

Да и на языках высокого уровня первый вариант запросто может быть читаемее. Битовые операции — их же один раз выучил и помнишь, а вот у той же RaiseEnumFlag может быть аж 4 разных сигнатуры:


void RaiseEnumFlag(int& state, int flag);
void RaiseEnumFlag(int flag, int& state);
int RaiseEnumFlag(int state, int flag);
int RaiseEnumFlag(int flag, int state);
constexpr auto shiftSize = sizeof(std::underlying_type_t<Flags>) * 8 - 1;

  • Не 8, а CHAR_BIT.
  • Всё равно будет неправильно, поскольку у целочисленного типа могут быть биты-заполнители (padding bits), которые здесь тоже подсчитаются.

Правильно (если тип беззнаковый, каким он в любом случае должен быть):
#include <limits>
constexpr auto shiftSize = std::numeric_limits<std::underlying_type_t<Flags>>::digits - 1;
Да, так будет правильнее, спасибо, подкорректировал.
Какой вывод можно сделать? Помимо очевидного «не занимайтесь микрооптимизациями без надобности»

Это не совсем верно. Хотя вы уже сами убедились в этом. clang очень слаб как компилятор. Делать какие-то выводы исходя из его результатов — не особо верно.


К тому же, ваш подход к измерениям так же не совсем верен. Эта функции не имеет смысла как не-инлайн. И даже по дефолту компилятор её почти всегда заинлайнит. А там результаты будут совершенно иные.


разве что можно посоветовать всегда проверять результат работы в машинном коде — может оказаться так, что компилятор уже достаточно соптимизировал изначальный вариант

Это дельный совет. Обратная связь это основа оптимизации.


а ваши «гениальные» оптимизации ему будут непонятны, и назло вам он понатыкает условных переходов вместо умножений.

Нет, просто у вас достаточно слабый компилятор. Я очень редко видел случаи, когда гцц мои "гениальные" оптимизации не понимал. Но опять же — тут нужно понимать компилятор. И оптимизировать исходя из результата.

У меня хватает примеров когда гцц не мог в то, что умеет кланг/ллвм. Я бы не назвал его слабее гцц.

У меня хватает примеров когда гцц не мог в то, что умеет кланг/ллвм. Я бы не назвал его слабее гцц.

Это всё, скорее всего, примеры, которые мало что значат. Просто было много статей в которых в шланг добавляли новую бесполезную эвристику, а после писал об этом. Причём сравнивали обычно с древними версиями гцц. Скорее всего, все ваши примеры — это оно самое.


Я же говорю о другом типе оптимизаций. А то, что выше — это просто примитивные реплейсы по эвристикам. Тут, очевидно, что будет множество примеров где в одном компиляторе есть какая-то эвристика, а в другом нет. Я это даже за оптимизации не считаю.


За оптимизации я считаю то, что мне сложно/невозможно сделать руками. Во всём этот шланг меня подводил. У него слабый flow-анализ. Допустим, он не в банальный кейс f = [&]{++ptr;}; f(); *ptr = a; f(); *ptr = b;....


Да и бек у него под amd64 слабый. Рассказывают, что под arm сильный, но я не проверял. У гцц он намного сильнее.

Это не flow-анализ. Это очередной реплейс. К тому же пример специально подобран и вообще не состоятелен. Потому как gcc тупо ничего не сделает с кейсом T && T и все ифы там просто пыль в глаза.

это flow-анализ. x && y — это не выражение, а два разных бейзик блока. и кейс вполне реальный. Про "реплейс" — это скорее про гцц, то как он реализует пипхол оптимизации на базе простого DSL (найти-заменить), в llvm же это все с кодом + он постоянно пытается деоптимизировать выражения в более сложные дабы найти возможности для CSE.

это flow-анализ.
Нет.

x && y — это не выражение

Именно выражение. Блоки к теме отношения не имеют. гцц не просто не реплейсит x && y — всё остальное он делает.


и кейс вполне реальный

Нет, кейс фейковый. Это явно видно. Взят случай, где гцц ничего не делает — выражение x && y, далее придумывается какая-то лапша с ифами. И самое интересное — это работает. Вы продолжаете мне рассказывать про ифы, хотя они не при делах.


Про "реплейс" — это скорее про гцц, то как он реализует пипхол оптимизации на базе простого DSL (найти-заменить), в llvm же это все с кодом + он постоянно пытается деоптимизировать выражения в более сложные дабы найти возможности для CSE.

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


У меня есть множество примеров того как clang ничего не может. Причём реальных. Причём таких, где он сливает в 2раза.


Я постоянно собираю код двумя компиляторами и у меня есть соответствующий опыт. clang генерирует почти всегда мусор за редкими исключениями.

Именно выражение

Рекомендую почитать основы компиляторов перед тем как вступать в полемику.


У меня есть множество примеров того как clang ничего не може

Так поделитесь ими, ваш пример еще более бессмысленный чем мой (+ непонятно где именно там кланг не сработал). Мой реален, просто упрощен для годболта, а так я его достал из продакшена.

Рекомендую почитать основы компиляторов перед тем как вступать в полемику.

На каком основании вы мне что-то рекомендуете? К тому же, почему вдруг стало игнорироваться всё остальное, где именно говорится о проблеме и том, что ваш пример не состоятелен? Зачем вы туда напихали ифов? Для чего? Если проблема не в ифах?


К тому же, абсолютно неважно как там какой-то компилятор трактует что-то в общем случае. В данном случае это выражение, чем и пользуется шланг выпиливая оттуда && реплейсом.


То, что с flow-анализом у гцц нету никаких проблем — https://godbolt.org/z/G5aAK9 — можно посмотреть так. Т.е. проблема именно в том, что гцц не реплейсит bool на 1|0 и && на & как это делает clang.


Так поделитесь ими, ваш пример еще более бессмысленный чем мой (+ непонятно где именно там кланг не сработал).

Почему вы игнорируете пример выше с установкой флагов? Мои пример это не код, на котором clang не сработал. Этот пример паттерна на котором он не сработал. Реальный пример слишком сложно перепастить на godbolt и ещё сделать так, что-бы он его собрал.


Мой реален, просто упрощен для годболта, а так я его достал из продакшена.

Вы не смогли даже правильную причину вывести и напихали непонятно зачем туда ифов. Как-то странно выглядит "реальная проблема". В ситуации с реальными проблемами сразу же ищется решение и ищется причина.

В данном случае это выражение, чем и пользуется шланг выпиливая оттуда && реплейсом

"шланг" ничего не выпиливает — это просто фронтенд, он парсит С++ код в AST и эмитит промежуточный язык LLVM IR, который для данного случая выглядит как два отдельных выражения в разных бейсик блоках (для моего простого кода там их вообще 6) поэтому просто описать это как "ищи шаблон х && y и заменяй" не выйдет. Говорю это, т.к. сам пытался реализовывать такие оптимизации в C# JIT. Ещё раз повторю, пример вполне реален — выполняется два условия — возращаем одно значение, иначе — другое. Вот вам без вложенного ифа: https://godbolt.org/z/SMa2Zu (гцц ведет себя так, как будто я использовал __builtin_expect).


Реальный пример слишком сложно перепастить на godbolt и ещё сделать так, что-бы он его собрал.

Ну когда у вас будет пруф в виде годболт ссылки, я с удовольствием посмотрю, а пока это неаргументированный спор и раз вам лень его подкреплять хоть каким аругментом — мне тоже лень. Если у вас не получается сделать репро — то может быть не так уж и плохо всё у кланга?

Мне кажется, тут clang перестарался.
Немного меняем пример, и у gcc скорее всего будет лучше latency, потому что в выходе clang больше инструкций и все они последовательно зависимы (т.е. выполнение не распараллеливается)
godbolt.org/z/mZgnr9

А вот здесь clang вообще на каком основании сделал безусловное чтение cond2? Переменная находится на другой странице, и эта страница может быть paged out. Это баг компилятора.
godbolt.org/z/Sssp7J
Немного меняем пример, и у gcc скорее всего будет лучше latency, потому что у clang больше инструкций и все они зависимы (т.е. не распараллеливаются)

А вот это не факт, код кланга бранчлес, так что в зависимости от коллера может быть быстрее или медленее. Тем более с недавними патчами микрокода от Интел все джампы могут в любой момент солидно просесть. Попробуйте изменить свой пример добавив PGO профиль — кодген будет уже другим.

А вот это не факт, код кланга бранчлес
Я например слышал, что сейчас Intel рекомендует использовать бранчи вместо CMOVXX, потому что предиктор у них очень хороший, а CMOV перед выполнением всегда ждёт вычисления всех операндов.

CMOV в Skylake ускорили в два раза по летанси (и пропускной?) так что теперь он вроде всегда быстрее: https://raw.githubusercontent.com/xiadz/cmov/master/output/out-6700k.png (https://github.com/xiadz/cmov)


А джампы замедлили патчем (если джам/таргет пересекают случайно границу в 32 байта кода — то получают огромное пенальти)

Хотя, по вашей ссылке, в варианте CMOVCC код был короче, чем с JMPCC, и даже так в некоторых случаях они были равны. Здесь же (https://godbolt.org/z/mZgnr9) у clang всегда 7 инструкций до ret, а у gcc в лучшем случае 3, в худшем 6. То есть, не факт что clang быстрее.
(и пропускной?)

Очевидно, что нет. Она и так была минимальной.


так что теперь он вроде всегда быстрее

Он итак был всегда быстрее, а бенчмарк этот — мусор. Он ничего не мериет. Он не учитывает засирание фронта мусором от джампов. Даже если сама логика не зависит от него, что крайне симнительно, то как минимум от этого зависит соседний поток при наличии SMT.


Точно так же, данный мусорный бенчмарк бенчит летенси, а если код упирается в летенси — это плохой код. Как минимум рядом с ним можно написать что-то ещё.


Т.е. бенчмарк замеряет только один сомнительный кейс, когда SMT нет, когда ещё какой-то логики рядом нет, когда логика сама по себе итеративна, т.е. упирается в летенси.

Sign up to leave a comment.

Articles