Какие задачи решал на прошлой работе?

Ответ

На предыдущей позиции в качестве C++ разработчика я занимался созданием и оптимизацией низкоуровневых компонентов для высоконагруженной торговой платформы.

Ключевые задачи и достижения:

  1. Разработка и оптимизация ядра обработки рыночных данных (market data feed handler):

    • Переписал критический путь обработки сообщений с использованием SIMD-инструкций (AVX2) и оптимизации кэша, что снизило задержку (latency) на 25% для наиболее частых операций.
    • Реализовал lock-free ring buffer для передачи данных между потоками приема сетевых пакетов и потоков логической обработки, устранив contention.
  2. Создание системы кэширования с политикой вытеснения:

    • Разработал шаблонный 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

    };

  3. Портирование и рефакторинг:

    • Перенес критический модуль расчета рисков с C на современный C++17, заменив сырые указатели на std::unique_ptr и голые массивы на std::vector, что сократило количество ошибок сегментации на 40%.
    • Внедрил систему модульных тестов с использованием Google Test и интеграцию с CI/CD.
  4. Профилирование и отладка:

    • Регулярно использовал perf, Intel VTune и Valgrind/Callgrind для поиска узких мест (bottlenecks) и утечек памяти.
    • Работал с сетевым стеком: низкоуровневая оптимизация с использованием DPDK (Data Plane Development Kit) для одного из экспериментальных шлюзов.

Ответ 18+ 🔞

Ёпта, сидел я на прошлой работе, как самый настоящий кодоед на C++. Занимался тем, что ковырял низкоуровневые штуки для одной торговой платформы, где всё должно летать быстрее мысли, а иначе — будет вам хиросима и нигерсраки, простите за выражение. Нагрузки там были — овердохуища.

Чем конкретно мозги выносил и что сделал:

  1. Ядро для обработки рыночных данных (этот самый market data feed handler):

    • Взял и переписал самое пиздопроебибное место, где данные бегают туда-сюда. Засунул туда SIMD-инструкции (AVX2) и так всё по кэшам разложил, что задержка упала на 25%. Представляешь? Четверть времени просто выкинули, как мусор. Раньше думали, быстрее некуда, а оказалось — ещё как есть куда.
    • Сделал lock-free кольцевой буфер, чтобы потоки, которые пакеты из сети хватают, и потоки, которые их разбирают, не толкались локтями. Раньше была драка за ресурсы, а теперь — красота, всё течёт, как по маслу.
  2. Система кэширования с умной политикой (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

    };

  3. Портирование старого говнокода:

    • Был у нас один модуль по расчёту рисков, написанный на чистом Си, ещё, наверное, в каменном веке. Страшная каркалыга: указатели торчат, массивы голые, доверия ебать ноль. Я его целиком на C++17 перетащил: std::unique_ptr вместо указателей, std::vector вместо массивов. После этого количество падений с сегфолтами сократилось на 40%. Руководство само от себя охуело, что так можно было.
  4. Профилирование и поиск косяков:

    • Постоянно сидел с perf, Intel VTune и Valgrind'ом. Искал, где программа тормозит или память течёт. Это как быть сантехником: слушаешь, где булькает, и лезешь в самые глубокие трубы.
    • Один раз даже в DPDK лазил, для одного экспериментального шлюза. Это когда нужно с сетью работать на такой скорости, что стандартный стек ядра — это хуй с горы, слишком медленно. Прям почти железом приходилось думать. Ебать копать, конечно, но зато прикольно.

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