← Назад к вопросам

Приведи пример самой интересной задачи которую решал

1.0 Junior🔥 141 комментариев
#Soft skills и мотивация#Опыт работы и проекты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Интересная задача: Оптимизация high-frequency trading систем

Одна из наиболее интересных и сложных задач, которую я решал, была оптимизация системы обработки рыночных данных в реальном времени для high-frequency trading платформы. Эта задача объединила знания о многопоточности, сетевых протоколах, памяти и алгоритмах.

Исходная задача

Компания занималась торговлей на финансовых рынках и получала порядка 100,000 рыночных обновлений в секунду от различных бирж. Система должна была:

  1. Получить данные о котировках акций от множества источников (TCP соединения)
  2. Распарсить и нормализовать данные
  3. Обновить локальное состояние рынка
  4. Выполнить анализ в реальном времени и принять торговые решения
  5. Отправить приказы на биржу в течение нескольких микросекунд

Критические требования:

  • Latency: < 1 миллисекунда от получения данных до отправки приказа
  • Throughput: Обработать 100k+ сообщений в секунду
  • Reliability: Гарантированная обработка каждого сообщения
  • Scalability: Добавлять новые источники данных без переписывания

Исходная архитектура (Проблемы)

Первоначальная реализация использовала простой подход:

// Старый код: множество проблем
int main() {
    for (auto& source : data_sources) {
        socket = create_socket(source.address);
        
        while (true) {
            data = receive_data(socket);      // Блокирующий вызов!
            parsed = parse_market_data(data);  // CPU bound
            update_market_state(parsed);      // Доступ к общим данным
            check_trading_signals(parsed);    // Анализ
        }
    }
}

Проблемы:

  1. Блокирующие I/O: Один источник, который медленно отправляет данные, блокирует всю систему
  2. Lock contention: Все потоки конкурируют за доступ к market state
  3. Memory thrashing: Частые выделения памяти для каждого сообщения
  4. Cache misses: Неоптимальное использование CPU cache
  5. Context switching: Слишком много потоков = большой overhead

Решение: Асинхронная архитектура с lock-free структурами

1. Асинхронный ввод-вывод с Epoll

#include <sys/epoll.h>
#include <vector>
#include <queue>

class NetworkReceiver {
private:
    int epoll_fd;
    std::vector<int> sockets;
    std::queue<RawMessage> received_messages;
    std::mutex messages_mutex;
    
public:
    void setup_sources(const std::vector<std::string>& sources) {
        epoll_fd = epoll_create1(0);
        
        for (const auto& source : sources) {
            int sock = create_socket(source);
            sockets.push_back(sock);
            
            struct epoll_event event;
            event.events = EPOLLIN;
            event.data.fd = sock;
            epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event);
        }
    }
    
    void receive_loop() {
        struct epoll_event events[64];
        
        while (true) {
            int num_events = epoll_wait(epoll_fd, events, 64, 100);
            
            // Обрабатываем готовые сокеты
            for (int i = 0; i < num_events; ++i) {
                if (events[i].events & EPOLLIN) {
                    RawMessage msg = read_socket(events[i].data.fd);
                    
                    // Lock-free добавление в очередь (с lock для простоты)
                    {
                        std::lock_guard<std::mutex> lock(messages_mutex);
                        received_messages.push(msg);
                    }
                }
            }
        }
    }
};

Преимущество: Один поток может управлять множеством соединений, epoll эффективнее select/poll.

2. Lock-free кольцевой буфер для очереди сообщений

#include <atomic>

template <typename T, size_t CAPACITY>
class LockFreeRingBuffer {
private:
    std::array<T, CAPACITY> buffer;
    std::atomic<uint64_t> write_index{0};
    std::atomic<uint64_t> read_index{0};
    
public:
    bool push(const T& value) {
        uint64_t write_pos = write_index.load(std::memory_order_relaxed);
        uint64_t next_write = (write_pos + 1) % CAPACITY;
        
        // Проверяем, полна ли очередь
        if (next_write == read_index.load(std::memory_order_acquire)) {
            return false;  // Очередь переполнена
        }
        
        buffer[write_pos] = value;
        write_index.store(next_write, std::memory_order_release);
        return true;
    }
    
    bool pop(T& value) {
        uint64_t read_pos = read_index.load(std::memory_order_relaxed);
        
        if (read_pos == write_index.load(std::memory_order_acquire)) {
            return false;  // Очередь пуста
        }
        
        value = buffer[read_pos];
        read_index.store((read_pos + 1) % CAPACITY, std::memory_order_release);
        return true;
    }
};

Преимущество: Нет блокировок (locks), только атомарные операции. Сложность O(1), нет динамических выделений.

3. Pre-allocated объекты вместо динамических

class MarketDataCache {
private:
    struct ParsedMessage {
        uint32_t symbol_id;
        uint64_t bid_price;
        uint64_t ask_price;
        uint32_t bid_volume;
        uint32_t ask_volume;
        uint64_t timestamp;
    };
    
    // Pre-allocated пул объектов, не требует malloc
    std::array<ParsedMessage, 10000> messages_pool;
    std::queue<ParsedMessage*> free_messages;
    
public:
    MarketDataCache() {
        for (auto& msg : messages_pool) {
            free_messages.push(&msg);
        }
    }
    
    ParsedMessage* get_message() {
        if (free_messages.empty()) return nullptr;
        auto msg = free_messages.front();
        free_messages.pop();
        return msg;
    }
    
    void return_message(ParsedMessage* msg) {
        free_messages.push(msg);
    }
};

Преимущество: Избегаем malloc/new (медленно), используем pre-allocated память. Очень быстро.

4. Оптимизированная обработка состояния рынка

class MarketState {
private:
    // Используем std::atomic для lock-free обновлений
    struct SymbolData {
        std::atomic<uint64_t> bid_price{0};
        std::atomic<uint64_t> ask_price{0};
        std::atomic<uint32_t> bid_volume{0};
        std::atomic<uint32_t> ask_volume{0};
    };
    
    std::array<SymbolData, 10000> symbols;  // Индексирование по symbol_id
    
public:
    void update_symbol(uint32_t symbol_id, const ParsedMessage& msg) {
        // Нет lock_guard! Используем атомарные операции
        symbols[symbol_id].bid_price.store(msg.bid_price, 
                                           std::memory_order_release);
        symbols[symbol_id].ask_price.store(msg.ask_price, 
                                           std::memory_order_release);
        symbols[symbol_id].bid_volume.store(msg.bid_volume, 
                                            std::memory_order_release);
        symbols[symbol_id].ask_volume.store(msg.ask_volume, 
                                            std::memory_order_release);
    }
    
    SymbolData get_symbol_data(uint32_t symbol_id) const {
        SymbolData data;
        data.bid_price = symbols[symbol_id].bid_price.load(
            std::memory_order_acquire);
        data.ask_price = symbols[symbol_id].ask_price.load(
            std::memory_order_acquire);
        return data;
    }
};

Преимущество: Lock-free обновления, несколько потоков могут читать одновременно.

5. Трёхпоточная архитектура

int main() {
    LockFreeRingBuffer<RawMessage, 100000> input_buffer;
    LockFreeRingBuffer<TradingSignal, 10000> signal_buffer;
    MarketState market_state;
    
    // Поток 1: Получение данных (Network I/O)
    std::thread network_thread([&]() {
        NetworkReceiver receiver;
        receiver.setup_sources(data_sources);
        receiver.receive_loop();  // Blocking, но асинхронный
    });
    
    // Поток 2: Парсинг и обновление состояния (CPU bound)
    std::thread parsing_thread([&]() {
        RawMessage msg;
        MarketDataCache cache;
        
        while (true) {
            if (input_buffer.pop(msg)) {
                ParsedMessage* parsed = cache.get_message();
                if (parsed) {
                    fast_parse(msg, parsed);  // SIMD оптимизирован
                    market_state.update_symbol(parsed->symbol_id, *parsed);
                    cache.return_message(parsed);
                }
            }
        }
    });
    
    // Поток 3: Анализ и торговля (CPU bound, можно распараллелить)
    std::thread trading_thread([&]() {
        while (true) {
            // Читаем state без блокировок благодаря атомарности
            auto data = market_state.get_symbol_data(SYMBOL_ID);
            
            TradingSignal signal = analyze_market(data);
            if (signal.is_valid()) {
                signal_buffer.push(signal);  // Lock-free
                send_to_exchange(signal);    // Быстро!
            }
        }
    });
    
    network_thread.join();
    parsing_thread.join();
    trading_thread.join();
    
    return 0;
}

Результаты оптимизации

До оптимизации:

  • Latency: ~10-50 миллисекунд (неприемлемо)
  • Throughput: ~10k сообщений/сек
  • CPU usage: 85% на 8 ядрах (lock contention)

После оптимизации:

  • Latency: 100-500 микросекунд (в 100 раз лучше!)
  • Throughput: 200k+ сообщений/сек (20x улучшение)
  • CPU usage: 30% на 4 ядрах (эффективнее)

Ключевые техники оптимизации

  1. Асинхронный I/O (epoll вместо блокирующих сокетов)
  2. Lock-free структуры (кольцевой буфер с атомарными операциями)
  3. Pre-allocation (избегаем malloc во время обработки)
  4. SIMD парсинг (SSE инструкции для быстрого парсинга чисел)
  5. Cache-aware дизайн (выравнивание данных на cache line границах)
  6. Разделение ответственности (разные потоки для I/O, CPU bound, анализа)

Уроки

Эта задача научила меня:

  • Профилирование и измерение > gut feeling
  • Памяти и кэш иерархия очень важны
  • Lock-free программирование требует осторожности, но даёт огромный выигрыш
  • Modern C++ (atomic, memory_order) незаменим для high-performance систем
  • Системное программирование (epoll, OS механизмы) критично для backend

Заключение: Это была не просто оптимизация, а полная переархитектура системы. Комбинирование знаний о языке, операционных системах, алгоритмах и практической инженерии позволила достичь результатов, которые были необходимы для конкурентоспособности на финансовых рынках.

Приведи пример самой интересной задачи которую решал | PrepBro