Канал для поиска исполнителей для разных задач и организации мини конкурсов
Last updated 3 months ago
Новые и перспективные Web3 игры с добычей токенов.
Чат: https://t.me/Crypto_Wolf_Chat
Правила чата смотрите в описании чата.
Все свои вопросы направляйте в чат или главному модератору чата: @Exudna_118
По теме сотрудничества: @Zombini
Last updated 2 months, 2 weeks ago
Обзор жостиков C++ *?*** (номер 0b11
)
На этот раз будет аж семь жостиков - три про DIY в Clang, одно про устройство Wireshark, одно с видео, и пара про идиомы C++ ? 7️⃣ В данный момент это самый плотный набор жостиков.
1️⃣ [Clang] Сделай сам наследование в enum ?
У enum'ов в C++ нет наследования. Но это могло бы быть полезным в редких случаях, когда есть общий enum для всех кейсов, но в каких-то окружениях есть мелкие различия, которыми не хочется засорять общее место.
Пусть программа работает с большими ресторанами, куда отправляем запросы на доставку. С нашей стороны можно запросить новую доставку или отмену доставки (статус становится Adding
/Cancelling
и ждется ответ от сервера), после ответов от сервера статус становится Added
/Cancelled
/Delivered
.
Большинство ресторанов не имеет возможности изменить адрес доставки (только отменять и создавать новую), но пусть какое-нибудь "Кафе Пушкинъ" ? так умеет, тогда неплохо бы иметь enum-наследник:
enum class DeliveryStatus : uint8\_t {
Adding,
Added,
Cancelling,
Cancelled,
Delivered,
};
enum class CafePushkinDeliveryStatus : DeliveryStatus {
Moving,
};
(Планируется, что при изменении заказа статус меняется Added \-> Moving \-> Added
)
Я попробовал придумать дизайн для наследования ? Придумал такие правила:
1️⃣ У enum-наследника нумерация первой константы как будто она следует за последней константой родителя:
enum class First : short {
None, /* = 0 */
MovedPermanently = 301,
Found, /* = 302 */
SeeOther, /* = 303 */
};
enum class Second : First {
NotModified, /* = 304 */
UseProxy, /* = 305 */
BadRequest = 400,
Unauthorized, /* = 401 */
};
enum class Third : Second {
PaymentRequired, /* = 402 */
Forbidden, /* = 403 */
InternalServerError = 500,
};
2️⃣ enum-наследник наследует родительский underlying type транзитивно
static\_assert(sizeof(First) == 2);
static\_assert(sizeof(Second) == 2);
static\_assert(sizeof(Third) == 2);
static\_assert(std::is\_same\_v<std::underlying\_type\_t<First>, short>);
static\_assert(std::is\_same\_v<std::underlying\_type\_t<Second>, First>);
static\_assert(std::is\_same\_v<std::underlying\_type\_t<Third>, Second>);
3️⃣ enum-родитель может неявно кастоваться к своему enum-наследнику:
inline constexpr Third kFound = First::Found;
4️⃣ Скоуп enum-наследника включает в себя все символы enum-родителей - этот пример аналогичен прошлому:
inline constexpr Third kFound = Third::Found;
То есть lookup по Third::Found
находит enum constant First::Found
(имеет тип First
), потом на нем делается implicit cast к типу Third
?
Сделал proof of concept в Clang - ссылка на дифф?
(Это базовый вариант - нет тестов, не супер красивый код, не рассмотрена возможность множественного наследования, и так далее...)
Код компилятора жесткий, есть много хаков чтобы например различать a ? new enum E : int{}
от foo(a, enum E : int{})
.
Также под флагами поддерживается нестандартный синтаксис, например можно писать enum E : int *p;
что равносильно enum E : int; E *p;
(прикол от microsoft)
ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ ?
Обзор на работу в большой компании ?
В начале этого года я покинул Яндекс, где проработал чуть больше 4 лет ??
Хотя там была ровная братва среди коллег и «интересные проекты»™️, я внял мудрости современного философа - «за деньги да» ? Но обо всем по порядку...
Часть1️⃣ Рассказ бывалого балабола
Я накидал субъективный обзор ? на плюсы и минусы
*➕*1️⃣* *Hard skills
За несколько первых месяцев реальной работы в мощном темпе выучиваешь в разы больше, чем за годы «саморазвития» до этого ?
За долгий срок работы можно порешать задачи из широкого спектра, испытать все виды инцидентов, и в целом это интересный опыт ? Можно смотреть курсы ШАД, код крутых коллег, внутренние ресурсы.
Нельзя сказать, чтобы у каждого было супер крутое знание какого-либо языка - это не самоценность, а просто инструмент выполнения задачи. Нередко встречаются ржаки по типу как на этом скрине, что чела закинули в команду где пишут на Х и он тоже выучил Х за 21 день (можно догадываться про уровень понимания)?
Компании не нужно, чтобы ты был rockstar - большие проекты делаются сотнями людей, и адекватный менеджмент ориентируется на их средний уровень, а также резервирует время под возможные переделки ⏰
Если же менеджмент неадекватный, то будут требовать сделать космолет за неделю, что будет не под силу даже Леннарту Поттерингу ?
К вопросу о том - можно ли считать себя "ылитой", если ты работаешь в FAANG или под них косящих ? - имхо это звучит неуместно, когда речь идет о супер формализованном процессе найма workforce (результат каждого собеса это буквально true или false) с "good enough" компетенцией, чтобы закрывать стандартные таски. Так вижу.
*➕*2️⃣* *Soft skills
Если есть желание, софт скиллы тоже неплохо прокачиваются. Хотя их можно качать бесконечно - если какой-то алгоритм или фичу можно выучить раз и навсегда, то взаимодействие с людьми это то, чему можно учиться всю жизнь ?
Обычный "работяга" - низшее звено пищевой цепочки. То, что он делает какие-то таски - это значит что топы определили направление, менеджеры поборолись за скоуп, а тимлид нарезал таски из глубокого знания codebase.
И достаточно скоро надо будет самому быстро наводить связи и эффективно общаться по рабочим вопросам. Не могу посчитать, со сколькими коллегами была переписка, но это цифра порядка нескольких сотен ? Поэтому если есть когнитивные баги, которые мешают нормально общаться - будет трудно работать и расти, я это видел на примере нескольких супер-крутых технически коллег, у которых были сложности (плохо считывали эмоции и социальные ситуации).
Это тот случай, когда "негативный опыт - тоже опыт". Так как многое завязано на человеческий фактор, комфортное пребывание во многом зависит от твоих коллег и еще намного больше от рукля.
За все время у меня было 5 руклей, со всеми я супер ровно сработался, кроме одного. Так оно работает - чтобы у тебя были задачи с высоким импактом и рост, необходимое условие чтобы рукль (и дальше по цепочке вверх) этого тоже хотели. Некоторое время я с ним промучался, но ничего не помогало и все было настолько плохо, что мне даже ставили встречу с HR с неприятным разговором ? Так что совет - лучше сразу уходить, если есть негатив с руклем, а не пытаться это исправить.
Я чуть не перешел к кенту в стартап, но по итогу просто ротировался в другой под-отдел и неплохо поработал в компании еще 1.5 года ?
ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ ?
Обзор жостиков C++ *?*** (номер 0x00000002
)
1️⃣ Сделай сам requires-clause для полей класса ?
Шаблоны C++ развиваются в сторону упрощения - многое было сделано, чтобы избавиться от enable_if
и ручной специализации шаблонов.
Одна из фичей - сейчас можно наложить trailing requires-clause на класс и на метод, чтобы ограничить их использование:
template<typename T>
requires std::copyable<T> && std::movable<T>
struct Blow {
void make\_job()
requires (sizeof(T) > 100)
{ /* ... */ }
};
Логичным следующим шагом должно стать ограничение на поле класса.
Такое опциональное наличие поля было бы полезно везде, где из-за свойств шаблонной сущности меняется размер класса. Например делетеры в умных указателях (как std::unique_ptr
). Или отсутствие поля "size", если он указан в шаблоне (как std::span
).
Супер синтетический пример, как это должно работать у вектора, который на стеке или куче в зависимости от параметра:
```
template
class JanusVector {
private:
// static vector, N is fixed
std::array arr_ requires (N > 0);
// dynamic vector, N is not fixed
std::size_t size_ requires (N == 0);
T* data_ requires (N == 0);
};
// ...
int main() {
JanusVector vec1;
static_assert(sizeof(vec1) == 4 * 228);
JanusVector vec2;
static_assert(sizeof(vec2) == 16);
}
```
Недавно мной была вкинута идея в разнорабочую группу по C++, пока без эффекта ?
Решил для прикола сделать реализацию этой фичи на Clang ?
(Ссылки: как настроить сетап для clang, как создать свой keyword)
Дифф за пару вечеров получился не очень большим (он на gist.github.com), состоит из таких кусков:
1️⃣ В парсере разрешить парсить выражение trailing requires-clause после полей класса.
2️⃣ Сохранять это выражение в класс FieldDecl
, вычислять его значение в методе isRequiresClauseSatisfied
.
3️⃣ Добавить проверки в LayoutBuilder'е, чтобы не вкомпилировать storage под неудовлетворяющие поля.
4️⃣ Диагностировать обращения к неудовлетворяющим полям (создается ошибка компиляции).
5️⃣ Осторожно проинстанцировать выражение во время инстанциации шаблона ? Непроинстанцированные зависимые константные выражения невычисляемы!
Вот так можно делать proof of concept фичи ? Это самый базовый пример, еще не рассмотрено несколько концептуальных вопросов, например:
1️⃣ Разрешить ли одинаковые имена у полей, если они никогда не скомпилируются вместе в одном классе?
2️⃣ Нужно ли помещать поле в скоуп выражения, для такого:
// типа поле `kek\_` скомпилится если у него будет метод `foo()`
T kek\_ requires requires { kek\_.foo(); };
3️⃣ Всякие мелкие баги, наподобии того что в реализации не запрещено брать адрес &Foo::kek
у нескомпилировавшегося поля kek
.
В КОММЕНТАРИЯХ ЕЩЕ ТРИ ЖОСТИКА C++ ?
День, когда умер CRTP ?
По исходникам проектов на C++ можно определять, примерно в какое время был написан код ?? В истории C++ был революционный стандарт C++11, еще чуть менее революционный C++20 (по убыванию используемости - concepts, ranges, coroutines, modules), и проходные между ними (C++14, C++17).
В C++23 есть единственная крупная фича, ради которой стоило его ждать - deducing this ? Единственное место, где нужно про него читать - пропозал в стандарт, там есть всё - мотивация, обоснование всех тонкостей дизайна, примеры использования, разбор всех корнер кейсов, и прочее. Все другие объяснения из интернета принципиально менее полны ? Я сюда не буду копипастить примеры, они есть в пропозале.
Long story short, в каждом методе используется неявный "нулевой" параметр this
, эта фича добавляет возможность явно указать этот параметр, причем необязательно с исходным типом (можно заиспользовать шаблон). Это оказалось очень нужным - уменьшает дублирование методов, убирает необходимость в CRTP, позволяет удобнее рекурсивные лямбды, и прочее ?
Конечно, у deducing this есть ряд drawbacks ?♂️
1️⃣Есть ряд спецэффектов по типу shadowing'а переменной базового класса (в пропозале они разобраны)
2️⃣ Методы становятся шаблонными со всеми вытекающими - Clang известен тем, что в отличие от MSVC не особо проверяет код шаблона до инстанциации
3️⃣ В коде начинается треш с категориями значений, нужно понимать разницу между auto
, auto&&
, decltype(auto)
, и специально добавили срань как std::forward_like.
Реализация deducing this наконец-то появилась в LLVM/Clang 18, который зарелизился 6 марта ? К сожалению, релиз получился всратым, и баги не пофикшены до сих пор ? Поэтому его пока никто не использует.
Но уже сейчас можно изучать вопрос - как лучше всего подготовиться к использованию этой фичи и гарантировать, что все перестанут писать "по-старому"? ?
В этом нам поможет libclang
! Мы можем написать простые python-тесты на исходный код (о них писал тут?), и отлавливать устаревший код.
Первым делом нужно, чтобы в проекте генерировался compile_commands.json
. Это простой список команд для компилятора, его описание тут, он нужен для многих вещей, например для автокомплита.
Его создание поддерживается в CMake, для этого в корневом CMakeLists.txt
надо добавить:
set(CMAKE\_EXPORT\_COMPILE\_COMMANDS ON)
Потом надо поставить пакеты из этого поста? Код всего теста можно посмотреть тут, дальше я опишу что он делает.
ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ ?
Обзор жостиков C++ ? (номер sizeof(char)
)
(Предисловие)
Эту картинку я сделал месяц назад, и хотел отправить в WG21 (Working Group 21, он же Комитет C++) как предложение для логотипа ? Это буква "С" и два плюса.
Но вспомнив комитетские приколы, решил не делать ?
Какой-нибудь товарищ комиссар Рабочей Группы по многообразию не принял бы ее, аргументировав что для репрезентации нужна широкая цветовая палитра, а вместо одного из крестов, например, символ зороастризма ? Зачем это делать, если он сам не живет так, как говорил Заратустра - непонятно. Слишком многим людям форма важнее содержания.
Поэтому это будет новая ава ?
1️⃣ Strong-типы ?
Теория о них тут, реализация на гитхабе.
Strong-типы это обертки над базовыми типами (int
, double
и пр.), которые не могут конвертироваться в другие типы. Это полезно, чтобы не путать аргументы и понимать цель значения из имени типа.
Например, вместо такого конструктора:
Rectangle(double width, double height);
Можно сделать такой:
Rectangle(Width width, Height height);
Где Width
и Height
- обертки над double
, и их нельзя перепутать ✋
Strong-типам можно придавать разные "скиллы" - например Addable, Comparable, Hashable и прочие, чтобы соответственно работали операции сложения, сравнения или можно было положить тип в хэшмапу.
Эти типы "бесплатные" для рантайма - компилятор выбрасывает шелуху и работает с базовым типом ?
Раньше я их не принимал (если ты путаешься в параметрах, то вместо 15 параметров нужен builder pattern ?), но сейчас наоборот, без них жить сложнее - это неплохая вещь.
2️⃣ Упакованные типы ?
Как известно, у типов есть выравнивание:
struct Biba {
uint16\_t a; /* 2 bytes */
/* padding 6 bytes */
uint64\_t b; /* 8 bytes */
};
static\_assert(sizeof(Biba) == 16);
static\_assert(alignof(Biba) == 8);
Но во время парсинга пакетов всяких протоколов это может мешать, потому что никаких padding'ов между полями не предусмотрено. Также во время работы с сетью супер важно оптимизировать память. Тогда надо убирать выравнивание (alignment) ?
Это можно сделать разными способами, самый удобный такой:
\#pragma pack(push, 1)
struct Boba {
uint16\_t a; /* 2 bytes */
uint64\_t b; /* 8 bytes */
};
\#pragma pack(pop)
static\_assert(sizeof(Boba) == 10);
static\_assert(alignof(Boba) == 1);
(Этот pragma pack выглядит странно, потому что у него есть типа внутренний стек)
Команда pack(push, N)
значит что все типы будут иметь alignment максимум N
байт, а pack(pop)
убирает это.
Менее удобный способ (потому что надо ставить каждому классу вручную) это вешать на структуру __attribute__((packed))
.
Это немного по-другому работает внутри компилятора, но итог один - alignment равный единице.
Способ, который не работает - спецификатор alignas, он может только увеличить дефолтный alignment. Оно используется для подгона структуры под кэш-линию (64 байта) или SIMD-инструкции (16/32/64 байта).
А как это работает на уровне ассемблера? ? Компилятор не будет генерировать дичь с переносами байтов (как для bitfield), там формируется честный unaligned access.
Как оказывается, на x86 никаких сюрпризов нет. Оно всегда поддерживало unaligned access (работающий медленнее чем aligned).
А на других архитектурах может произойти треш - надо беречься ?
ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ ?
Обзор книги "Modern Parallel Programming with C++ and Assembly Language" (2022 г.) ?
(можно скачать PDF тут)
Как можно понять, книга посвящена "параллельному программированию".
Но тут имеется в виду не та параллельность когда есть много CPU (и используются мьютексы, etc.), а параллельность внутри одного CPU, а если точнее, то вся книга про SIMD (single instruction, multiple data) ?
Сейчас стандартные типы данных имеют размер 8/16/32/64 бит (соответственно это byte/word/dword/qword
), они "нативно" поддерживаются потому что сами регистры общего назначения у процессора имеют размер 64 бита ?
Но в процессорах часто есть регистры на 128, 256 и даже 512 бит (соответственно это xmmword/ymmword/zmmword
в x86) ? В эти регистры "упаковываются" значения стандартного размера и над ними затем делаются групповые операции.
Проще показать на примере - пусть мы суммируем float
'ы:
// float* z, const float* x, const float* y, size\_t n
for (size\_t i = 0; i < n; i++)
z[i] = x[i] + y[i];
То SIMD-версия на 256-битных регистрах могла бы выглядеть так (с поправкой на конкретный компилятор, т.к. эти интринсики не специфицированы в Стандарте С++):
// представим что `n` делится на 8
for (size\_t i = 0; i < n; i += 8) {
\_\_m256 x\_vals = \_mm256\_loadu\_ps(&x[i]); // грузим x[i..i+8] в один регистр
\_\_m256 y\_vals = \_mm256\_loadu\_ps(&y[i]); // грузим y[i..i+8] в другой регистр
\_\_m256 z\_vals = \_mm256\_add\_ps(x\_vals, y\_vals); // вычисляем z[i..i+8] в третьем
\_mm256\_storeu\_ps(&z[i], z\_vals); // выгружаем z[i..i+8] в память
}
Код выше работает быстро, решительно, в разы быстрее "наивного" варианта.
Общий flow такой - в "длинный" регистр выгружается мини-массив чисел (в примере выше массив из 8 float
'ов), и ускорение достигается за счет того, что процессор не тратит время на чтение одних и те же опкодов, а сразу делает нужную операцию.
Так как процессоры сейчас гига сложные (я наклал кирпичей даже когда делал эмулятор m68k 45-летней давности!), то таких "групповых операций" наделали много. Можно, например, вычислять еще z[i] = min(x[i], y[i])
, или y[i] = x[2*i] + x[2*i+1]
, или даже быстро переставить элементы z[i] = x[y[i]]
, и так далее.
Книга посвящена только архитектуре x86 (архитектуры как ARM не рассматриваются).
SIMD-расширений в x86 есть несколько. Сначала в 1997 году появился MMX от Intel, потом в 1998 году 3DNow от AMD, и так далее, многие давно устарели и не выпускаются.
Книга посвящена только сравнительно новым SIMD-расширениям AVX (2011 год), AVX2 (2013 год) и AVX-512 (2017 год).
Глава 1️⃣ описывает базу SIMD.
В главах 2️⃣➖8️⃣ по одному шаблону описываются фичи AVX / AVX2 / AVX-512:
1️⃣ Описывается какая-нибудь платиновая задача - найти минимум/среднее в массиве, перемножить матрицы, применить свёртку, etc.
2️⃣ Приводится портянка кода на C++: "наивная реализация" vs "реализация на SIMD", с нудным описанием что откуда идёт.
3️⃣ Приводится бенчмарк, наивная реализация проигрывает SIMD в среднем в 10-15 раз.
В главе 9️⃣ описывается как можно было бы сделать портабельную SIMD-программу - для этого в x86 есть опкод cpuid
, по которому можно узнать поддерживаемые SIMD-расширения и еще много что.
В главе 1️⃣0️⃣ неплохо описывается ~~боян~~ архитектура процессора x86-64 вместе с этими SIMD-регистрами в 256/512 бит.
В главах 1️⃣1️⃣➖1️⃣8️⃣ описывается то же, что в главах 2-8, но на ассемблере... Я это не читал ?
В главе 1️⃣9️⃣ описывается здравый смысл, то есть гайд по SIMD-оптимизациям, общая тема - оптимизировать надо не всё подряд, а только то что видно в профайлере, потому что SIMD-код понимать трудно и легко ошибиться.
В аппендиксах есть инфа как ставить вижуэл студио и ссылки на доки... ?
В целом полезная книга, можно почитать для общего развития. Только нужно иметь в виду:
1️⃣ Некоторые задачи лучше решаются через GPU, а не через SIMD на CPU (который ускорит лишь в единицы раз, а не в сотни).
2️⃣ Современный компилятор может сам сгенерировать SIMD-код (но это бабка надвое сказала).
3️⃣ Сначала профайлер, потом оптимизации.
Канал для поиска исполнителей для разных задач и организации мини конкурсов
Last updated 3 months ago
Новые и перспективные Web3 игры с добычей токенов.
Чат: https://t.me/Crypto_Wolf_Chat
Правила чата смотрите в описании чата.
Все свои вопросы направляйте в чат или главному модератору чата: @Exudna_118
По теме сотрудничества: @Zombini
Last updated 2 months, 2 weeks ago