
Төмен кідірісті сауда жүйелері үшін C++
Ең жылдам сауда фирмалары неге барлығын C++-та жазады
C++ неге жылдамдық тілі болып қалады
Кідіріске сезімтал стекте C++ сәйкестендіру жолын әлі де иелейді: Globex стилдегі платформалар, көптеген акция фидтері, крипто қозғалтқыштары (Hyperliquid класты инфрақұрылымды қоса) — ортасында тоқтап тұрған GC үзілісі жоқ болжамды машиналық кодқа дейін компиляцияланады.
C++-ты төмен кідірісті трейдингке айрықша сай ететін не?
- Қоқыс жинағыш жоқ — Болжанбайтын үзіліс жоқ. Жад қашан бөлінетінін және босатылатынын дәл өзіңіз бақылайсыз.
- Аппараттыққа жақындық — CPU кэш жолдарына, SIMD нұсқауларына, жадпен байланысқан I/O мен өзекті айналып өтетін желіге тікелей қол жеткізу.
- Компиляция уақытында есептеу — Шаблондық метабағдарламалау жұмысты орындалу уақытынан компиляция уақытына жылжытады, қолмен жазылған ассемблер тәрізді жылдам код шығарады.
- Болжамды кідіріс — Мұқият кодтау арқылы минималды джиттермен секундтың миллионнан бір бөлігінен де төменгі tick-to-trade кідірісіне жете аласыз.
Шындықты еске салу: Ең жылдам десктердегі tick-to-trade бюджеттері көбіне секундтың миллионнан бір бөлігінен де аз. Бір жаңылыс бөлінген жад немесе кэш-мисс бүкіл бюджетті жеп қоя алады — сол себепті командалар өндірістік желі тәрізді ыстық жолды қорғайды.
Жадты басқару: стек, куп, меп аллокаторлар
Төмен кідірісті 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Құлыпсыз деректер құрылымдары мен конкуренттілік
Мутекстер — төмен кідірісті кодтың жауы. Бір 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 ағыны кезекке қосады, стратегия ағыны алады — топологияны дұрыс алсаңыз, байланыс жолында мутекс жоқ.
Шаблондар: компиляция уақытында есептеу
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++ дәстүрлі патша болып қала береді.

