GaiaExGaiaEx
Төмен кідірісті сауда жүйелері үшін C++
ӘзірлеушіПрограммалау13 min read

Төмен кідірісті сауда жүйелері үшін C++

Ең жылдам сауда фирмалары неге барлығын C++-та жазады

Жазбаларды бөлісу

C++ неге жылдамдық тілі болып қалады

Кідіріске сезімтал стекте C++ сәйкестендіру жолын әлі де иелейді: Globex стилдегі платформалар, көптеген акция фидтері, крипто қозғалтқыштары (Hyperliquid класты инфрақұрылымды қоса) — ортасында тоқтап тұрған GC үзілісі жоқ болжамды машиналық кодқа дейін компиляцияланады.

C++-ты төмен кідірісті трейдингке айрықша сай ететін не?

  • Қоқыс жинағыш жоқ — Болжанбайтын үзіліс жоқ. Жад қашан бөлінетінін және босатылатынын дәл өзіңіз бақылайсыз.
  • Аппараттыққа жақындық — CPU кэш жолдарына, SIMD нұсқауларына, жадпен байланысқан I/O мен өзекті айналып өтетін желіге тікелей қол жеткізу.
  • Компиляция уақытында есептеу — Шаблондық метабағдарламалау жұмысты орындалу уақытынан компиляция уақытына жылжытады, қолмен жазылған ассемблер тәрізді жылдам код шығарады.
  • Болжамды кідіріс — Мұқият кодтау арқылы минималды джиттермен секундтың миллионнан бір бөлігінен де төменгі tick-to-trade кідірісіне жете аласыз.

Шындықты еске салу: Ең жылдам десктердегі tick-to-trade бюджеттері көбіне секундтың миллионнан бір бөлігінен де аз. Бір жаңылыс бөлінген жад немесе кэш-мисс бүкіл бюджетті жеп қоя алады — сол себепті командалар өндірістік желі тәрізді ыстық жолды қорғайды.

Ыстық жол неге туыс кодта қалады Детерминистік • Жолда GC тоқтау нүктесі жоқ • Айқын жад & орналасу • SIMD / кэш бақылауы • Өзекті айналып өтуге қолайлы нс/мкс-пен өлшенеді • p99 > p50 маңызды • Джиттер co-location артықшылығын өлтіреді • Қайта ойнатылатын бинарлықтар • Тұрақты пулдар / аренлар Интероп • NIC / FPGA сатушылары C/C++ ұсынады • DPDK, өзек модульдері • FIX / бинарлық фидтер • ОЖ-мен бірдей ABI
Биржа стектері болжамды кідіріс пен аппараттыққа тығыз байланысты жоғары бағалайды — C++ осы жұмыс үшін әдепкі құрал.

Жадты басқару: стек, куп, меп аллокаторлар

Төмен кідірісті C++-та жадты бөлу тәсілі есептеу мазмұнынан да маңызды. Стек пен куп бөлудің арасындағы айырма кідірісте 100 есе болуы мүмкін.

// Stack allocation — near-instant, deterministic
struct OrderUpdate {
    uint64_t order_id;
    double price;
    uint32_t quantity;
    char side;  // 'B' or 'S'
};

void process_tick(const MarketData& tick) {
    OrderUpdate update{};  // Stack — zero allocation cost
    update.price = tick.mid_price();
    // ... process on the hot path
}

// Heap allocation — slow, non-deterministic (avoid on hot path)
auto* order = new OrderUpdate{};  // Calls malloc — BAD on hot path

Өндірістік жүйелер меп аллокаторларға сүйенеді, сондықтан ыстық жол жалпы кубтке ешқашан хабарласпайды:

  • Пул аллокаторлары — Үлкен блокты алдын ала бөліп, оны тұрақты өлшемдегі бөліктерге кеседі. Фрагментация жоқ, O(1) бөлу.
  • Арена аллокаторлары — Әр бөлу үшін көрсеткішті алға жылжытады, бәрін бір мезгілде босатады. Хабар бойынша өңдеу үшін тамаша.
  • Үлкен беттер — 2МБ немесе 1ГБ беттер TLB-мисстерін азайтады, тапсырыс кітабы деректеріңіз мегабайттарды қамтыған кезде маңызды.

Заманауи C++ RAII (Resource Acquisition Is Initialization) мен ақылды көрсеткіштер арқылы қауіпсіз жад басқаруды қолайлы етеді:

// RAII — resource lifetime tied to scope
{
    auto connection = std::make_unique<TcpConnection>(endpoint);
    connection->send(order_message);
}  // Connection automatically closed here

// Shared ownership for reference-counted resources
auto config = std::make_shared<TradingConfig>(load_config());
engine.set_config(config);  // Multiple owners, automatic cleanup
Ыстық жолдағы стек пен куп Стек / thread-local OrderUpdate стекте — шектелген, LIFO, кэш-ыстық Пул / арена — O(1) қайта пайдалану, malloc шуы жоқ Куп (жалпы) new/malloc — аллокатор құлыптары, фрагментация Болжанбайтын кідіріс — әр тикте болдырмау
Структураларды стекте немесе алдын ала қыздырылған пулдарда ұстаңыз; куп соғысын tick handler-де қате деп қарастырыңыз.

Құлыпсыз деректер құрылымдары мен конкуренттілік

Мутекстер — төмен кідірісті кодтың жауы. Бір std::mutex::lock() шақыруы тіпті бақталассыз да 20-100 наносекунд алуы мүмкін — ал бақталастық кезінде ол ағынды микросекундтарға тоқтата алады. Трейдинг жүйелері оның орнына lock-free деректер құрылымдарын пайдаланады.

Трейдингтегі ең критикалық lock-free құрылым — Single-Producer Single-Consumer (SPSC) кезегі:

template<typename T, size_t Capacity>
class SPSCQueue {
    alignas(64) std::array<T, Capacity> buffer_;
    alignas(64) std::atomic<size_t> head_{0};
    alignas(64) std::atomic<size_t> tail_{0};

public:
    bool try_push(const T& item) {
        const auto tail = tail_.load(std::memory_order_relaxed);
        const auto next = (tail + 1) % Capacity;
        if (next == head_.load(std::memory_order_acquire))
            return false;  // Full
        buffer_[tail] = item;
        tail_.store(next, std::memory_order_release);
        return true;
    }

    bool try_pop(T& item) {
        const auto head = head_.load(std::memory_order_relaxed);
        if (head == tail_.load(std::memory_order_acquire))
            return false;  // Empty
        item = buffer_[head];
        head_.store((head + 1) % Capacity, std::memory_order_release);
        return true;
    }
};

Негізгі дизайн қағидалары:

  • alignas(64) — Әр атомды айнымалы өз кэш жолын алады, CPU өзектерінің арасындағы жалған бөлісуден сақтайды.
  • Жад реттілігіacquire/release семантикасы seq_cst-тен арзан және producer-consumer үлгісі үшін жеткілікті.
  • Екінің дәрежесі бойынша өлшем — Өндірісте 1024 немесе 4096 сияқты өлшемдерді қолданыңыз, сол кезде модуль амалы биттік AND-ке айналады.

Тарихи сымдау: NIC ағыны кезекке қосады, стратегия ағыны алады — топологияны дұрыс алсаңыз, байланыс жолында мутекс жоқ.

SPSC сақина буфері (бір жазушы, бір оқушы) Producer фид / I/O ағыны Екінің дәрежесіндегі слоттар • acquire/release атомдар Consumer стратегия ағыны alignas(64) head/tail — жалған бөлісуді өлтіру үшін бөлек кэш жолдары Жад реттілігі: жергілікті индексте relaxed, тапсыру кезінде acquire/release
Бір producer және бір consumer мутекстерден бас тартуға мүмкіндік береді; дұрыс тураланым өзектердің бір кэш жолы үшін таласуын болдырмайды.

Шаблондар: компиляция уақытында есептеу

C++ шаблондары жұмысты орындалу уақытынан компиляция уақытына жылжытуға мүмкіндік береді. Трейдингте бұл — бинарыңыз саудалайтын нақты хаттамалар, құралдар және стратегияларға арнап мамандандырылған дегенді білдіреді — ыстық жолда орындалу уақытында тармақтану жоқ.

// Compile-time FIX protocol field parsing
template<int Tag>
struct FIXField;

template<> struct FIXField<35> {  // MsgType
    static constexpr const char* name = "MsgType";
    using type = char;
};

template<> struct FIXField<44> {  // Price
    static constexpr const char* name = "Price";
    using type = double;
};

// Zero-overhead dispatch based on message type
template<char MsgType>
void handle_message(const char* raw, size_t len) {
    if constexpr (MsgType == 'D') {
        // New Order Single — inline at compile time
        parse_new_order(raw, len);
    } else if constexpr (MsgType == '8') {
        // Execution Report
        parse_execution(raw, len);
    }
}

if constexpr тармақтары толығымен компиляция уақытында шешіледі — генерацияланған машиналық код тек тиісті жолды ғана қамтиды, тармақтану шығыны нөлге тең. Бұл техника, link-time optimization (LTO) арқылы біріктірілгенде, хаттама талдауы негізінен жад оқуларының түзу тізбегіне «жазылған» бинарлықтарды шығарады.

Заманауи C++20/23 функциялары — consteval, концептер және компиляция уақытындағы контейнерлер — мұны одан әрі алға апарады, толық тапсырысты тексеру құбырларын компиляция уақытында есептеуге мүмкіндік береді.

Кэшке ыңғайлы дизайн және өзекті айналып өтетін желі

Секундтың миллионнан бір бөлігінен де аз кідірісте CPU кэш иерархиясы сіздің ең маңызды оптимизация нысанаңызға айналады. Негізгі жадқа кэш-миссі ~100 наносекунд тұрады — жалпы кідіріс бюджетіңіз 500 нс болса, бұл мәңгілік.

// BAD: Array of Pointers (AoP) — cache-hostile
std::vector<std::unique_ptr<Order>> orders;  // Each access = pointer chase + cache miss

// GOOD: Struct of Arrays (SoA) — cache-friendly
struct OrderBook {
    std::vector<double> prices;      // Contiguous in memory
    std::vector<uint32_t> quantities; // Contiguous in memory
    std::vector<uint64_t> order_ids;  // Contiguous in memory
};
// Iterating prices = sequential cache line reads = fast

Желі үшін Linux өзегінің TCP/IP стегі бір пакетке 5-15 микросекунд кідіріс қосады. Трейдинг фирмалары оны толығымен айналып өтеді:

  • DPDK (Data Plane Development Kit) — NIC-ты user space-тен тікелей полл жасайды, өзекті айналып өтеді. Секундтың миллионнан бір бөлігінен де аз пакетті өңдеуге жетеді.
  • Solarflare OpenOnload — Таныс socket API-мен өзекті айналып өту. Акциялар мен фьючерс трейдингінде кеңінен қолданылады.
  • FPGA NIC-тер — Xilinx Alveo және соған ұқсас карталар нарық деректерін талдап, тапсырыстарды аппаратта генерациялай алады, наносекунд деңгейіндегі кідіріске жетеді.

Орталықсыздандырылған биржалар да осы қағидалардан пайда көреді. GaiaEx сияқты платформаларды қуаттайтын Hyperliquid-тің L1 тізбегі жоғары өткізу қабілеті мен төмен кідірісті консенсус ойымен әзірленген, және оған қосылатын маркет-мейкерлер баға жаңартуын алу мен тапсырыс жіберу арасындағы уақытты минимизациялау үшін оптимизацияланған C++ клиенттерін пайдаланады.

Биржа сәйкестендіру қозғалтқыштары қалай құрылады

Әрбір биржаның жүрегінде сәйкестендіру қозғалтқышы тұрады — сатып алу мен сату тапсырыстарын жұптастыратын компонент. Ол — қаржы саласындағы ең кідіріске сезімтал бағдарлама, және ол дерлік әрдайым C++-та жазылады.

Қарапайымдатылған сәйкестендіру қозғалтқышының архитектурасы:

class MatchingEngine {
    // One order book per instrument
    std::unordered_map<Symbol, OrderBook> books_;

    void on_new_order(const Order& order) {
        auto& book = books_[order.symbol];
        auto matches = book.match(order);

        for (const auto& fill : matches) {
            publish_execution(fill);      // To trading firms
            update_market_data(fill);     // To data feed
        }

        if (order.remaining_qty > 0) {
            book.insert(order);           // Rest on the book
        }
    }
};

Өндірістік сәйкестендіру қозғалтқыштары бұл сүлбеден әлдеқайда алға оптимизацияланады:

  • Баға-уақыт басымдығы — Бірдей бағадағы тапсырыстар келу тәртібімен толады, наносекунд белгілерімен бақыланады.
  • Алдын ала бөлінген тапсырыс пулдары — Сәйкестендіру кезінде куп бөлу жоқ. Тапсырыстар тұрақты пулдардан қайта пайдаланылады.
  • Құлыпсыз дизайн — Әр құралдың кітабы бөлек өзекте жұмыс істейді. Кітаптар арасында құлыптау қажет емес.
  • Детерминистік қайта ойнату — Әр тапсырыс пен сәйкестік реттеу және апаттан кейін қалпына келтіру үшін тұрақты сақтауда журналданады.

Осы деңгейде жүйелер құру — C++ шынымен жарқырайтын жер. Ешбір басқа кең тараған тіл жад орналасуын, ағын жоспарлауды, кэш мінез-құлқын және желі I/O-ын — ультра-төмен кідірісті инженерияның төрт тірегін — бір мезгілде бақылауға мүмкіндік бермейді. Кейінгі биржаны құрсаңыз да, маркет-мейкер ретінде оған қосылсаңыз да, немесе prop фирмасында орындауды оптимизацияласаңыз да, C++ дәстүрлі патша болып қала береді.