Ответ
На предыдущей позиции в качестве C++ разработчика я занимался созданием и оптимизацией низкоуровневых компонентов для высоконагруженной торговой платформы.
Ключевые задачи и достижения:
-
Разработка и оптимизация ядра обработки рыночных данных (market data feed handler):
- Переписал критический путь обработки сообщений с использованием SIMD-инструкций (AVX2) и оптимизации кэша, что снизило задержку (latency) на 25% для наиболее частых операций.
- Реализовал lock-free ring buffer для передачи данных между потоками приема сетевых пакетов и потоков логической обработки, устранив contention.
-
Создание системы кэширования с политикой вытеснения:
- Разработал шаблонный thread-safe кэш LRU (Least Recently Used) для хранения предварительно рассчитанных ценовых производных.
template<typename Key, typename Value> class ConcurrentLRUCache { private: using ListType = std::list<std::pair<Key, Value>>; ListType items_list; // Хранит пары ключ-значение в порядке использования std::unordered_map<Key, typename ListType::iterator> items_map; // Для быстрого доступа O(1) std::shared_mutex mutex_; // Используем shared_mutex для read-heavy нагрузки size_t capacity_;
public: std::optional
get(const Key& key) { std::sharedlock lock(mutex); // Множественные читатели auto it = items_map.find(key); if (it == items_map.end()) return std::nullopt; // Перемещаем элемент в начало списка (наиболее недавно использованный) items_list.splice(items_list.begin(), items_list, it->second); return it->second->second; } // ... put, erase, resize};
- Разработал шаблонный thread-safe кэш LRU (Least Recently Used) для хранения предварительно рассчитанных ценовых производных.
-
Портирование и рефакторинг:
- Перенес критический модуль расчета рисков с C на современный C++17, заменив сырые указатели на
std::unique_ptrи голые массивы наstd::vector, что сократило количество ошибок сегментации на 40%. - Внедрил систему модульных тестов с использованием Google Test и интеграцию с CI/CD.
- Перенес критический модуль расчета рисков с C на современный C++17, заменив сырые указатели на
-
Профилирование и отладка:
- Регулярно использовал perf, Intel VTune и Valgrind/Callgrind для поиска узких мест (bottlenecks) и утечек памяти.
- Работал с сетевым стеком: низкоуровневая оптимизация с использованием DPDK (Data Plane Development Kit) для одного из экспериментальных шлюзов.
Ответ 18+ 🔞
Ёпта, сидел я на прошлой работе, как самый настоящий кодоед на C++. Занимался тем, что ковырял низкоуровневые штуки для одной торговой платформы, где всё должно летать быстрее мысли, а иначе — будет вам хиросима и нигерсраки, простите за выражение. Нагрузки там были — овердохуища.
Чем конкретно мозги выносил и что сделал:
-
Ядро для обработки рыночных данных (этот самый market data feed handler):
- Взял и переписал самое пиздопроебибное место, где данные бегают туда-сюда. Засунул туда SIMD-инструкции (AVX2) и так всё по кэшам разложил, что задержка упала на 25%. Представляешь? Четверть времени просто выкинули, как мусор. Раньше думали, быстрее некуда, а оказалось — ещё как есть куда.
- Сделал lock-free кольцевой буфер, чтобы потоки, которые пакеты из сети хватают, и потоки, которые их разбирают, не толкались локтями. Раньше была драка за ресурсы, а теперь — красота, всё течёт, как по маслу.
-
Система кэширования с умной политикой (LRU):
- Написал шаблонный, потокобезопасный кэш, который выкидывает то, чем давно не пользовались. Нужно было хранить всякие расчёты, чтобы по сто раз не пересчитывать. Сделал на
std::listиunordered_map, прикрутилshared_mutex, потому что читают его часто, а пишут редко. Получилась хитрая жопа, но работала как часы.template<typename Key, typename Value> class ConcurrentLRUCache { private: using ListType = std::list<std::pair<Key, Value>>; ListType items_list; // Тут пары ключ-значение в порядке "кто последний трогал" std::unordered_map<Key, typename ListType::iterator> items_map; // Чтобы найти за O(1) std::shared_mutex mutex_; // shared_mutex — наше всё, когда читателей дохуя size_t capacity_;
public: std::optional
get(const Key& key) { std::sharedlock lock(mutex); // Много читателей могут зайти auto it = items_map.find(key); if (it == items_map.end()) return std::nullopt; // Только что тронули — отправляем в начало списка, молодца items_list.splice(items_list.begin(), items_list, it->second); return it->second->second; } // ... дальше put, erase, resize};
- Написал шаблонный, потокобезопасный кэш, который выкидывает то, чем давно не пользовались. Нужно было хранить всякие расчёты, чтобы по сто раз не пересчитывать. Сделал на
-
Портирование старого говнокода:
- Был у нас один модуль по расчёту рисков, написанный на чистом Си, ещё, наверное, в каменном веке. Страшная каркалыга: указатели торчат, массивы голые, доверия ебать ноль. Я его целиком на C++17 перетащил:
std::unique_ptrвместо указателей,std::vectorвместо массивов. После этого количество падений с сегфолтами сократилось на 40%. Руководство само от себя охуело, что так можно было.
- Был у нас один модуль по расчёту рисков, написанный на чистом Си, ещё, наверное, в каменном веке. Страшная каркалыга: указатели торчат, массивы голые, доверия ебать ноль. Я его целиком на C++17 перетащил:
-
Профилирование и поиск косяков:
- Постоянно сидел с perf, Intel VTune и Valgrind'ом. Искал, где программа тормозит или память течёт. Это как быть сантехником: слушаешь, где булькает, и лезешь в самые глубокие трубы.
- Один раз даже в DPDK лазил, для одного экспериментального шлюза. Это когда нужно с сетью работать на такой скорости, что стандартный стек ядра — это хуй с горы, слишком медленно. Прям почти железом приходилось думать. Ебать копать, конечно, но зато прикольно.
В общем, работа была такая, где нельзя быть распиздяем. Каждая микросекунда на счету, каждая утечка памяти — потенциальный пиздец. Но когда видишь, как после твоих оптимизаций графики задержек падают — удовольствие пиздец.