Заметки так себе разработчика
91 subscribers
22 photos
2 videos
29 links
Грейды выше 17 в канал не принимаются

Автор: @garbart
Download Telegram
Простите меня, дорогие подписчеки, за отсуствие постов. Бездельничал и дурачился. Собирал для вас пак тем, про котоыре хочу рассказать. Первая на очереди — многопоточка.
Начнем с чего попроще, закончим какими-нибудь паттернами и подходами. Возможно разберем какую-нибудь прикладную задачку. Короче, должно быть интересно!
3
#прикольчики C++ №9

Начнем с простой фишечки. Когда вы пишете многопоточные программы, бывают случаи, когда каждому потоку нужен свой собственный экземпляр переменной. И тут умные разработчики из комитета по стандартизации C++ придумали ключевое слово thread_local.
Это спецификатор, который указывает, что переменная должна быть уникальной для каждого потока. Другими словами, каждый поток создаёт свою "копию" переменной, независимую от других.

Пример:
#include <iostream>
#include <thread>

thread_local int localCounter = 0;

void incrementCounter(const std::string& threadName) {
localCounter++;
std::cout << "Thread " << threadName << ": " << localCounter << "\n";
}

int main() {
std::thread t1(incrementCounter, "A");
std::thread t2(incrementCounter, "B");

t1.join();
t2.join();

return 0;
}

Выведет:
Thread A: 1
Thread B: 1


В целом бывает достаточно полезно иногда, пару раз встречал в продакшн-коде. Но с ними следует быть достаточно аккуратными: во-первых, это не всегда быстрее условного мьютекса сверху (если объект очень большой, например), а во-вторых, даже динамически выделенная память под thread_local переменные будет очищена, когда поток завершится.
2
Кстати, мне нравится текущее число подписчиков: 5 в двоичной системе счисления. Запрещаю всем отписываться и подписываться.
42
Не заводите ТГ канал, пацаны, вы матерям еще нужны, его пиздец как лень вести
💯11
Я тут короче смесяц назад наткнулся на статью одного господина из кембриджа. Он в своей статье рассказывает, что придумал способ вставлять элементы (без реордеринга) в плоскую хеш-таблицу оптимальнее, чем при использовании равномерного хеширования. Что, так-то, 40 лет невозможным считалось. Очень и очень рекомендую ознакомиться: "Optimal Bounds for Open Addressing Without Reordering".

Впрочем, вы может уже и видели, много пабликов про это писало с кликбейтными заголовками.
🔥21
Ну и мне как-то захотелось написать свою реализацию и сравнить ее с другими. Получилось реально хорошо. Можете взглянуть на моем гитхабе: https://github.com/garbart/FlatHashTable.

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

Но даже так результаты очень приятные. Сравнивал с absl::flat_hash_map и встроенной std::unordered_map. К результатам, 10M элементов:
absl::flat_hash_map benchmark:
Put time: 26947 ms
Get time: 8824 ms
Iterate time: 1522 ms
Remove time: 13798 ms
std::unordered_map benchmark:
Put time: 10444 ms
Get time: 1674 ms
Iterate time: 713 ms
Remove time: 4140 ms
FunnelFlatHashTable benchmark:
Put time: 3416 ms
Get time: 1844 ms
Iterate time: 435 ms
Remove time: 2182 ms

По времени вставки FunnelFlatHashTable получилась заметно быстрее std::unordered_map, и это самое главное. Немного дольше получение по значению — почти наверняка я налажал где-то в коде, как будто бы так не должно быть. Быстрее итерация по значениям (что логично, это же плоская таблица все-таки) и быстрее удаление.
Я, честно, не понял почему мапа от absl работает так медленно. Просвятите меня, если понимаете. Мне это кажется контринтуитивным.

В целом я экспериментом супер доволен. Когда нибудь эту мапку допишу до продакш-реди и реально буду использовать в своих проектах.
🔥2👏11
Если вы, кстати, тотально не понимаете о чем я и что за "плоская хеш-таблица", можете перечитать один из моих постов: https://t.me/partypooper_cpp/50
1🤔1
Я не релоцировался, честно
Forwarded from Experimental chill
Trivially relocatable

В C++ есть большая группа людей (включая меня), которая любит брать оптимизации из C -- mem* функции возможно являются сильнейшим преимуществом C перед многими другими языками в перформансе. В C++ об этом думали и делали аттрибут std::trivially_copyable, который разрешает копировать как memcpy. Отлично работает на примитивных структурах и в целом C++ такой быстрый в том числе и из-за этого.

Но бывают и более интересные кейсы. Когда вы добавляете элементы в std::vector, рано или поздно вам надо будет реаллоцировать. Вы будете копировать числа/делать std::move объектов из предыдущего вектора. В ситуации с числами можно звать memcpy, они тривиальны, всё отлично. Но на самом деле звать memcpy можно и не только на тривиально копируемые типы, а, например, на unique_ptr<T>, или QString (который с ref count), или vector<int>!

Ведь копирование указателей, чисел вида размера/вместимости при тривиальном std::move ни к чему плохому не приведёт. К сожалению, по стандарту так нельзя. Когда вы начинаете делать mem* функции на типы, у которых примитивный std::move, то стандарт ничего для этого не приготовил и только говорит, что вы обязаны вызвать деструктор, и memcpy не позволяет начать жизнь более сложных объектов.

Поэтому в последние лет 6 идёт борьба за то, чтобы внести понятие тривиально релоцируемых типов -- у которых move оператор и move конструкторы устроены так, что это просто копирование по битам. Если быть точным, нужно ещё, чтобы деструктор не делал странных вещей, например, ничего не делал для пустых векторов, нулевых указателей и тд.

Без этого вставки в середину вектора, удаления из векторов долго не могли использовать memcpy тоже для таких типов. Существует ряд оптимизаций, который открывается из этого. В том числе interop с Rust станет слегка попроще :)

6 лет бились (первые года 4 не сильно, комитету в целом нравилось предложение), и таки уехало в феврале этого года в C++26.

Есть отличный блог из 5 небольших частей почитать о том, как это устроено в Qt.

Сам пропозал
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2786r13.html#abstract
2