Всё хорошо, но формул для вычисления средних очень много. Вот у тебя, Хабра читатель, есть машина? Она показывает среднюю скорость, средний расход? А как она это делает, никогда не задумывался?
strict aliasing, -Wstrict-aliasing=3 вам в помощь, правда при -O2 и выше.
object lifetime violations (ваш std::launder тут спасает, но ваши примеры плохи), тут санитайзеры в помощь.
type-punning (классика UB, особенно через union или reinterpret_cast между несовместимыми структурами)
alignment issues (на x86 пофиг, но я на продакшене словил от более нового gcc movdqu вместо movdqa, хотя они стоят одинаковое количество тактов) -Wcast-align в помощь
struct Widget {
const int id;
std::string name;
};
alignas(Widget) std::byte buf[sizeof(Widget)];
auto *p = new (buf) Widget{1, "old"};
p->~Widget();
new (buf) Widget{2, "new"};
std::cout << p->id << '\n'; // UB: p указывает на покойника
меняем на
...
p->~Widget();
auto *q = new (buf) Widget{2, "new"};
std::cout << q->id << '\n'; // И здесь всё хорошо
Это простой "use after destruction" или "stale pointer", пока ничего необычного.
Тема оптимизации C++ шаблонов не раскрыта. Необходимо было рассказать, как в библиотеках типа STL, Boost, ... с этим борются, а там есть несколько способов.
Как реализованы разные hash-map. Статическая таблица вызовов функций, инициализируется для каждого типа.
struct RawTableOps {
size_t (*hash)(const void* key);
...
class RawTable {
private:
const RawTableOps* ops_;
...
template<class K, class V,
class H = std::hash<K>,
class E = std::equal_to<K>>
class HashMap : private RawTable {
public:
HashMap() : RawTable(&ops) {}
...
Как реализованы разные вектора. Все тяжёлые алгоритмы работают в базовом классе, приходится гонять через void* и size_t всё, и шаблонная обёртка преобразует к нужному типу.
class _Vec_common {
protected:
...
template<class T, class Alloc = std::allocator<T>>
class tiny_vector : private _Vec_common {
...
private:
T* data_{}; size_t size_{}, cap_{};
Про использование std::function добавлю. В C++26 появился std::function_ref (P0792) - не владеет callable-объектом, а держит reference-wrapper + тривиальный function-pointer thunk. Его размер - два указателя, ни аллокаций, ни type-erasure-state.
В коде ниже не может произойти RVO, потому что возвращаемый тип не соответствует тому, что мы возвращаем. Нам нужно построить std::optional объект, и тут вызовется конструктор перемещения, что не так плохо, но не RVO. В версией с const действительно конструктор перемещения не может быть использован, и будет конструктор копирования.
Видел такое. Типичный "фикс" - это sleep запихнуть. В дебаге всё работает, потому что в этом месте какой-то print в Log есть, и вся синхронизация тредов работает хорошо.
А теперь признайтесь - сколько у вас в коде sleep таких зарыто?
Напомнило, на одном серьёзном проекте надо было тестировать ещё и пользовательскую документацию. QA-девочка с задачей справлялись сильно лучше мальчиков, потому что они реально выполняли команды буквы в букву "cd ..." и такого каталога нет ;-) а мальчик даже не будет такое проверять, всё же норм.
С таким ручным layout вы легко можете нарваться на операцию чтения/записи по невыровненному адресу. А если вы ешё руками начнёте делать reinterpret_cast, то проблемы type punning, strict aliasing, ...
Хорошо что вы про std::bitcast упомянули, но статья была бы намного интереснее, если рассказали про std::launder, std::start_lifetime_as, etc. Иначе тема не раскрыта.
А каким тут боком тэг Си++ тут стоит? Нам, плюсовикам, немного чуждый ваш мир фронт-энда. Хотя, прочитав статью, нашёл нечто похожее, что делали в играл, которые на С/С++, чтобы грузить уровень не целиком, ожидая полминуты, а по ходу пьесы.
А как мне асинхронищину на Boost::ASIO переписать на этот новый "stdexec"? Мне никто не предлагает pool/epoll/uring/.... Наверное, пул-тредов для файловых операций, будет интересно, но сетевые вещи как?
Пушкенка (pushkin.local) - это особый случай локалки в федо. Она честно выполняла свою обязанность быть локалкой, т.е. распространяться среди линков узла. Особенно там было супер много (однодневных) поентов, полученных на автомате. Но даже из нескольких сотен "зевак", пришедших по авторегистрации из приложки (HotDogEd), задержались в федо с десяток. Потом локалка расползлась дальше по хабам, и стала наравне бонной эхи. Когда Пушкен само-выпил-ился из федонетовки, то эха продолжила своё существование уже как ящерица без головы (т.е. хвоста). Естессно, когда безмодераторная эха расходится по многим узлам, то нашлись желающие свои потоки ховн туда постить.
Статья про то, что современный компилятор из нашей наивной реализации memcpy(), с побайтным копированием, на -O3 оптимизации, сделает SIMD оптимизированную версию. Причём напишет версию очень похожую на libc с intrinsics, где скопирует начало до выровненного участка, дальше 512-битными числами, если AVX-512 есть, и так далее, и в конце хвостовую часть.
Каждый компилятор обычно предоставляет свой builtin __builtin_memcpy, который при передачи ему constexpr длины, заранее знает, какими большими регистрами ему копировать, без рантайм ветвлений.
Фича std::array, по сравнению с указателем на память, в том, что есть constexpr значение размера, и компилятор знает как именно скопировать лучше.
Спасибо за статью. Было бы ещё здорово сравнить с
std::pmr
контейнерами cmonotonic_buffer_resource
.a + (b - a) /2
тоже переполняется, еслиb > a
.Если вам по классике, тогда вот так
auto avg = (a & b) + ((a ^ b) >> 1);
Всё хорошо, но формул для вычисления средних очень много.
Вот у тебя, Хабра читатель, есть машина? Она показывает среднюю скорость, средний расход? А как она это делает, никогда не задумывался?
Вы главное забыли. Статья должна была быть про
strict aliasing, -Wstrict-aliasing=3 вам в помощь, правда при -O2 и выше.
object lifetime violations (ваш std::launder тут спасает, но ваши примеры плохи), тут санитайзеры в помощь.
type-punning (классика UB, особенно через union или reinterpret_cast между несовместимыми структурами)
alignment issues (на x86 пофиг, но я на продакшене словил от более нового gcc movdqu вместо movdqa, хотя они стоят одинаковое количество тактов) -Wcast-align в помощь
Ваш код
меняем на
Это простой "use after destruction" или "stale pointer", пока ничего необычного.
Меня такое на интервью спросили кстати.
Тема оптимизации C++ шаблонов не раскрыта. Необходимо было рассказать, как в библиотеках типа STL, Boost, ... с этим борются, а там есть несколько способов.
Как реализованы разные hash-map. Статическая таблица вызовов функций, инициализируется для каждого типа.
Как реализованы разные вектора. Все тяжёлые алгоритмы работают в базовом классе, приходится гонять через void* и size_t всё, и шаблонная обёртка преобразует к нужному типу.
Про использование
std::function
добавлю. В C++26 появилсяstd::function_ref
(P0792) - не владеет callable-объектом, а держит reference-wrapper + тривиальный function-pointer thunk. Его размер - два указателя, ни аллокаций, ни type-erasure-state.В коде ниже не может произойти RVO, потому что возвращаемый тип не соответствует тому, что мы возвращаем. Нам нужно построить std::optional объект, и тут вызовется конструктор перемещения, что не так плохо, но не RVO. В версией с const действительно конструктор перемещения не может быть использован, и будет конструктор копирования.
Чтобы в вашем примере случился RVO надо написать сразу вот так
P.S. В этом, кстати проблема современных std::optional, std:expected и им подобных.
Таки причём тут C++? Опять хештеги путаете?
Задача на собеседовании - что выдаст на экран эта программа ;-)
Но это, конечно, обфускация и ненормативное программирование.
Видел такое. Типичный "фикс" - это sleep запихнуть. В дебаге всё работает, потому что в этом месте какой-то print в Log есть, и вся синхронизация тредов работает хорошо.
А теперь признайтесь - сколько у вас в коде sleep таких зарыто?
Напомнило, на одном серьёзном проекте надо было тестировать ещё и пользовательскую документацию. QA-девочка с задачей справлялись сильно лучше мальчиков, потому что они реально выполняли команды буквы в букву "cd ..." и такого каталога нет ;-) а мальчик даже не будет такое проверять, всё же норм.
С таким ручным layout вы легко можете нарваться на операцию чтения/записи по невыровненному адресу. А если вы ешё руками начнёте делать reinterpret_cast, то проблемы type punning, strict aliasing, ...
Хорошо что вы про std::bitcast упомянули, но статья была бы намного интереснее, если рассказали про std::launder, std::start_lifetime_as, etc. Иначе тема не раскрыта.
А каким тут боком тэг Си++ тут стоит?
Нам, плюсовикам, немного чуждый ваш мир фронт-энда. Хотя, прочитав статью, нашёл нечто похожее, что делали в играл, которые на С/С++, чтобы грузить уровень не целиком, ожидая полминуты, а по ходу пьесы.
А как мне асинхронищину на Boost::ASIO переписать на этот новый "stdexec"? Мне никто не предлагает pool/epoll/uring/.... Наверное, пул-тредов для файловых операций, будет интересно, но сетевые вещи как?
Стандарт тупо ещё не умеет работать с юникодом, с файлами в разных кодировках. Потом уже будет сеть и всё остальное.
Тема C++17 не раскрыта. Пост про трудовые будни геймдева, и про плюсы тут только, что долго собирается. Пишите на голом Си.
Пушкенка (pushkin.local) - это особый случай локалки в федо. Она честно выполняла свою обязанность быть локалкой, т.е. распространяться среди линков узла. Особенно там было супер много (однодневных) поентов, полученных на автомате. Но даже из нескольких сотен "зевак", пришедших по авторегистрации из приложки (HotDogEd), задержались в федо с десяток. Потом локалка расползлась дальше по хабам, и стала наравне бонной эхи. Когда Пушкен само-выпил-ился из федонетовки, то эха продолжила своё существование уже как ящерица без головы (т.е. хвоста). Естессно, когда безмодераторная эха расходится по многим узлам, то нашлись желающие свои потоки ховн туда постить.
Статья про то, что современный компилятор из нашей наивной реализации
memcpy()
, с побайтным копированием, на-O3
оптимизации, сделает SIMD оптимизированную версию. Причём напишет версию очень похожую на libc с intrinsics, где скопирует начало до выровненного участка, дальше 512-битными числами, если AVX-512 есть, и так далее, и в конце хвостовую часть.Каждый компилятор обычно предоставляет свой builtin
__builtin_memcpy
, который при передачи ему constexpr длины, заранее знает, какими большими регистрами ему копировать, без рантайм ветвлений.Фича std::array, по сравнению с указателем на память, в том, что есть constexpr значение размера, и компилятор знает как именно скопировать лучше.