
लो-लेटेंसी ट्रेडिंग सिस्टम्स के लिए C++
सबसे तेज़ ट्रेडिंग फर्म्स सब कुछ C++ में क्यों लिखती हैं
C++ ही स्पीड की भाषा क्यों है
लेटेंसी-सेंसिटिव स्टैक में, C++ अब भी मैचिंग पाथ पर राज करता है: Globex-स्टाइल वेन्यूज़, कई इक्विटी फ़ीड्स, और क्रिप्टो इंजन (Hyperliquid-क्लास इंफ्रास्ट्रक्चर सहित) — सब प्रेडिक्टेबल मशीन कोड में कंपाइल होते हैं, जिसके बीच में कोई GC पॉज़ छुपा नहीं होता।
C++ को लो-लेटेंसी ट्रेडिंग के लिए इतना ख़ास क्या बनाता है?
- कोई गार्बेज कलेक्टर नहीं — कोई अप्रत्याशित पॉज़ नहीं। आप ठीक-ठीक तय करते हैं कि मेमोरी कब अलोकेट और कब फ्री होगी।
- हार्डवेयर के करीब — CPU कैश लाइन, SIMD इंस्ट्रक्शन, मेमोरी-मैप्ड I/O और कर्नल बायपास नेटवर्किंग तक सीधी पहुँच।
- कंपाइल-टाइम कम्प्यूटेशन — टेम्प्लेट मेटाप्रोग्रामिंग रनटाइम का काम कंपाइल टाइम पर खिसका देती है, जिससे मिलने वाला कोड हाथ से लिखी असेंबली जैसा ही तेज़ होता है।
- प्रेडिक्टेबल लेटेंसी — सावधानी से कोड करने पर, आप कम-से-कम जिटर के साथ सब-माइक्रोसेकंड टिक-टू-ट्रेड लेटेंसी पा सकते हैं।
असलियत की जाँच: सबसे तेज़ डेस्क्स पर टिक-टू-ट्रेड बजट अक्सर सब-माइक्रोसेकंड होते हैं। एक भटकी हुई एलोकेशन या एक कैश मिस पूरा बजट खा सकता है — इसलिए टीमें हॉट पाथ की रक्षा किसी प्रोडक्शन लाइन जैसी सख़्ती से करती हैं।
मेमोरी मैनेजमेंट: स्टैक, हीप और कस्टम अलोकेटर
लो-लेटेंसी C++ में, आप मेमोरी कैसे अलोकेट करते हैं यह इससे ज़्यादा मैटर करता है कि आप क्या कम्प्यूट कर रहे हैं। स्टैक और हीप अलोकेशन के बीच का फ़र्क लेटेंसी में 100x तक हो सकता है।
// 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) अलोकेशन।
- एरीना अलोकेटर — हर अलोकेशन पर एक पॉइंटर को आगे बढ़ाएँ, सबको एक साथ फ्री करें। पर-मैसेज प्रोसेसिंग के लिए एकदम सही।
- ह्यूज पेजेज़ — 2MB या 1GB पेज 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 नैनोसेकंड ले सकती है — और कंटेंशन में, यह किसी थ्रेड को माइक्रोसेकंड्स तक रोक सकती है। ट्रेडिंग सिस्टम इसके बजाय लॉक-फ्री डेटा स्ट्रक्चर्स इस्तेमाल करते हैं।
ट्रेडिंग में सबसे अहम लॉक-फ्री स्ट्रक्चर है 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 कोर्स के बीच false sharing रोकती है।- मेमोरी ऑर्डरिंग —
acquire/releaseसेमेंटिक्सseq_cstसे सस्ते होते हैं और प्रोड्यूसर-कंज़्यूमर पैटर्न के लिए काफ़ी होते हैं। - पावर-ऑफ-टू साइज़िंग — प्रोडक्शन में, 1024 या 4096 जैसे साइज़ इस्तेमाल करें, ताकि modulo एक bitwise 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 ब्रांचेज़ पूरी तरह कंपाइल टाइम पर हल हो जाती हैं — बना हुआ मशीन कोड सिर्फ़ काम की पाथ रखता है, बिना किसी ब्रांचिंग ओवरहेड के। यह तकनीक, लिंक-टाइम ऑप्टिमाइज़ेशन (LTO) के साथ मिलकर, ऐसे बाइनरी बनाती है जिनमें प्रोटोकॉल पार्सिंग लगभग एक सीधी-रेखा मेमोरी-रीड सीक्वेंस में बदल जाती है।
मॉडर्न C++20/23 की सुविधाएँ जैसे consteval, कॉन्सेप्ट्स, और कंपाइल-टाइम कंटेनर्स इसे और आगे ले जाते हैं, जिससे पूरे ऑर्डर वैलिडेशन पाइपलाइन कंपाइल टाइम पर ही कम्प्यूट हो सकते हैं।
कैश-फ्रेंडली डिज़ाइन और कर्नल बायपास नेटवर्किंग
सब-माइक्रोसेकंड लेटेंसी पर, CPU कैश हायरार्की आपका सबसे अहम ऑप्टिमाइज़ेशन टारगेट बन जाती है। मेन मेमोरी पर एक कैश मिस लगभग ~100 नैनोसेकंड लेता है — जब आपका कुल लेटेंसी बजट 500ns हो, तो यह एक युग जितना लंबा वक़्त है।
// 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 को पोल करता है, कर्नल को बायपास करते हुए। सब-माइक्रोसेकंड पैकेट प्रोसेसिंग हासिल करता है।
- Solarflare OpenOnload — एक जाने-पहचाने सॉकेट API के साथ कर्नल बायपास। इक्विटी और फ़्यूचर्स ट्रेडिंग में व्यापक रूप से इस्तेमाल होता है।
- FPGA NIC — Xilinx Alveo और इस तरह के कार्ड्स हार्डवेयर में मार्केट डेटा पार्स करके ऑर्डर जनरेट कर सकते हैं, और नैनोसेकंड-स्तर की लेटेंसी हासिल करते हैं।
डिसेंट्रलाइज़्ड एक्सचेंज भी इन सिद्धांतों से फ़ायदा उठाते हैं। Hyperliquid की L1 चेन — जो GaiaEx जैसे प्लेटफ़ॉर्म्स को पावर देती है — हाई-थ्रूपुट, लो-लेटेंसी कंसेंसस को ध्यान में रखकर डिज़ाइन की गई थी, और इससे जुड़ने वाले मार्केट मेकर्स प्राइस अपडेट मिलने और ऑर्डर सबमिट करने के बीच के वक़्त को कम करने के लिए ऑप्टिमाइज़्ड 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 — इन चारों स्तंभों — पर एक साथ इतना कंट्रोल नहीं देती, जो अल्ट्रा-लो-लेटेंसी इंजीनियरिंग की बुनियाद हैं। चाहे आप अगला एक्सचेंज बना रहे हों, किसी से मार्केट मेकर की तरह जुड़ रहे हों, या किसी प्रॉप फ़र्म में एग्ज़िक्यूशन ऑप्टिमाइज़ कर रहे हों — C++ ही निर्विवाद राजा बना रहता है।