GaiaEx AcademyGaiaEx Academy
کم تاخیر (low-latency) والے ٹریڈنگ سسٹمز کے لیے C++
ڈویلپرپروگرامنگ13 min read

کم تاخیر (low-latency) والے ٹریڈنگ سسٹمز کے لیے C++

تیز ترین ٹریڈنگ فرمز سب کچھ C++ میں کیوں لکھتی ہیں

پوسٹس شیئر کریں

رفتار کی زبان C++ کیوں ہے

لیٹنسی-حساس اسٹیک میں، C++ اب بھی میچنگ پاتھ کا مالک ہے: Globex-طرز کے مقامات، بہت سی ایکویٹی فیڈز، اور کرپٹو انجنز (بشمول Hyperliquid-کلاس انفراسٹرکچر) قابلِ پیش گوئی مشین کوڈ میں کمپائل ہوتے ہیں بغیر بیچ میں کسی GC pause کے چھپے۔

C++ کو کم-لیٹنسی ٹریڈنگ کے لیے منفرد طور پر موزوں کیا بناتا ہے؟

  • کوئی گاربیج کلیکٹر نہیں — کوئی غیر متوقع رکاوٹیں نہیں۔ آپ درست طریقے سے کنٹرول کرتے ہیں کہ میموری کب مخصوص اور آزاد کی جاتی ہے۔
  • ہارڈویئر قربت — CPU کیش لائنز، SIMD ہدایات، میموری-میپڈ I/O، اور کرنل بائی پاس نیٹ ورکنگ تک براہِ راست رسائی۔
  • کمپائل-ٹائم کمپیوٹیشن — ٹیمپلیٹ میٹا پروگرامنگ کام کو runtime سے compile time تک منتقل کرتی ہے، ایسا کوڈ پیدا کرتی ہے جو ہاتھ سے لکھی گئی اسمبلی جیسا تیز ہو۔
  • قابلِ پیش گوئی لیٹنسی — احتیاط سے کوڈنگ کے ساتھ، آپ کم سے کم jitter کے ساتھ سب-مائیکرو سیکنڈ tick-to-trade لیٹنسی حاصل کر سکتے ہیں۔

حقیقت کا جائزہ: سب سے تیز ڈیسکس پر tick-to-trade بجٹس اکثر سب-مائیکرو سیکنڈ ہوتے ہیں۔ ایک بے موقع allocation یا کیش مِس پورا لفافہ کھا سکتا ہے—تو ٹیمیں hot path کو ایک پروڈکشن لائن کی طرح محفوظ رکھتی ہیں۔

ہاٹ پاتھ نیٹو کوڈ میں کیوں رہتا ہے قطعی (Deterministic) • پاتھ پر کوئی GC سیف پوائنٹس نہیں • واضح میموری اور لے آؤٹ • SIMD / کیش کنٹرول • کرنل بائی پاس دوستانہ ns/µs میں ماپا گیا • p99 > p50 اہم ہے • Jitter co-location کی برتری کھاتا ہے • دوبارہ چلانے کے قابل بائنریز • مقررہ پولز / arenas Interop • NIC / FPGA فروش C/C++ فراہم کرتے ہیں • DPDK، کرنل ماڈیولز • FIX / بائنری فیڈز • OS جیسا ABI
ایکسچینج اسٹیکس قابلِ پیش گوئی لیٹنسی اور مضبوط ہارڈویئر ملاپ کو ترجیح دیتے ہیں—C++ اس کام کی وضاحت کے لیے ڈیفالٹ اوزار ہے۔

میموری مینجمنٹ: اسٹیک، ہیپ، اور کسٹم Allocators

کم-لیٹنسی C++ میں، آپ میموری کیسے مخصوص کرتے ہیں اس سے زیادہ اہم ہے کہ آپ کیا کمپیوٹ کرتے ہیں۔ اسٹیک اور ہیپ allocation کے درمیان فرق لیٹنسی میں 100x ہو سکتا ہے۔

// Stack allocation — تقریباً فوری، قطعی
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 — صفر allocation قیمت
    update.price = tick.mid_price();
    // ... hot path پر پروسیس کریں
}

// Heap allocation — سست، غیر-قطعی (hot path پر بچیں)
auto* order = new OrderUpdate{};  // malloc کال کرتا ہے — hot path پر BAD

پروڈکشن سسٹمز کسٹم allocators پر انحصار کرتے ہیں تاکہ hot path کبھی جینرک ہیپ کو کال نہ کرے:

  • پول Allocators — ایک بڑا بلاک پیشگی مخصوص کرتے ہیں اور مقررہ سائز کے حصے کاٹتے ہیں۔ کوئی fragmentation نہیں، O(1) allocation۔
  • اریینا Allocators — ہر allocation کے لیے ایک پوائنٹر آگے بڑھاتے ہیں، سب کچھ ایک ساتھ آزاد کرتے ہیں۔ فی-میسج پروسیسنگ کے لیے بہترین۔
  • ہیوج پیجز — 2MB یا 1GB صفحات TLB مِسز کم کرتے ہیں، جو اہم ہے جب آپ کے آرڈر بک ڈیٹا میگا بائٹس تک پھیلا ہو۔

جدید C++ محفوظ میموری مینجمنٹ کو RAII (Resource Acquisition Is Initialization) اور اسمارٹ پوائنٹرز کے ساتھ آسان بناتا ہے:

// RAII — وسائل کی زندگی scope سے منسلک
{
    auto connection = std::make_unique<TcpConnection>(endpoint);
    connection->send(order_message);
}  // کنیکشن یہاں خودکار طور پر بند ہو جاتا ہے

// حوالہ-شمار وسائل کے لیے مشترکہ ملکیت
auto config = std::make_shared<TradingConfig>(load_config());
engine.set_config(config);  // متعدد مالکان، خودکار صفائی
hot path پر اسٹیک بمقابلہ ہیپ اسٹیک / thread-local OrderUpdate اسٹیک پر — محدود، LIFO، کیش-گرم پول / اریینا — O(1) دوبارہ استعمال، کوئی malloc اضطراب نہیں ہیپ (جینرک) new/malloc — allocator locks، fragmentation غیر متوقع لیٹنسی — فی tick بچیں
structs کو اسٹیک پر یا پیشگی گرم کیے گئے پولز میں رکھیں؛ tick ہینڈلر پر heap hits کو bugs سمجھیں۔

لاک-فری ڈیٹا اسٹرکچرز اور کنکرنسی

Mutexes کم-لیٹنسی کوڈ کے دشمن ہیں۔ ایک واحد std::mutex::lock() کال بغیر مسابقت کے بھی 20-100 نینو سیکنڈز لے سکتی ہے—اور مسابقت کے تحت، یہ ایک thread کو مائیکرو سیکنڈز کے لیے روک سکتی ہے۔ ٹریڈنگ سسٹمز اس کے بدلے lock-free ڈیٹا اسٹرکچرز استعمال کرتے ہیں۔

ٹریڈنگ میں سب سے اہم lock-free اسٹرکچر Single-Producer Single-Consumer (SPSC) queue ہے:

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;  // بھرا ہوا
        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;  // خالی
        item = buffer_[head];
        head_.store((head + 1) % Capacity, std::memory_order_release);
        return true;
    }
};

کلیدی ڈیزائن اصول:

  • alignas(64) — ہر atomic متغیر کو اپنی الگ کیش لائن ملتی ہے، جو CPU کورز کے درمیان false sharing کو روکتی ہے۔
  • میموری آرڈرنگacquire/release semantics seq_cst سے سستے ہیں اور producer-consumer پیٹرنز کے لیے کافی ہیں۔
  • Power-of-two سائزنگ — پروڈکشن میں، 1024 یا 4096 جیسے سائز استعمال کریں تاکہ modulo ایک bitwise AND بن جائے۔

عام وائرنگ: NIC thread enqueue کرتا ہے، strategy thread dequeue کرتا ہے—اگر آپ topology درست حاصل کریں تو wire path پر کوئی mutex نہیں۔

SPSC رنگ بفر (ایک لکھنے والا، ایک پڑھنے والا) پروڈیوسر فیڈ / I/O thread Power-of-two سلاٹس • acquire/release atomics کنزیومر strategy thread alignas(64) head/tail — علیحدہ کیش لائنز false sharing ختم کرتی ہیں میموری آرڈر: لوکل انڈیکس پر relaxed، handoff کے پار acquire/release
ایک پروڈیوسر اور ایک کنزیومر آپ کو mutexes چھوڑنے دیتا ہے؛ درست alignment کورز کو ایک ہی کیش لائن پر لڑنے سے روکتی ہے۔

ٹیمپلیٹس: کمپائل-ٹائم کمپیوٹیشن

C++ ٹیمپلیٹس آپ کو runtime سے compile time کی طرف کام منتقل کرنے دیتے ہیں۔ ٹریڈنگ میں، اس کا مطلب ہے آپ کا بائنری ان مخصوص پروٹوکولز، انسٹرومنٹس، اور حکمتِ عملیوں کے لیے مخصوص ہے جو آپ ٹریڈ کرتے ہیں—hot path پر کوئی runtime branching نہیں۔

// کمپائل-ٹائم FIX پروٹوکول فیلڈ پارسنگ
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;
};

// میسج ٹائپ کی بنیاد پر صفر-اووور ہیڈ ڈسپیچ
template<char MsgType>
void handle_message(const char* raw, size_t len) {
    if constexpr (MsgType == 'D') {
        // New Order Single — کمپائل ٹائم پر inline
        parse_new_order(raw, len);
    } else if constexpr (MsgType == '8') {
        // Execution Report
        parse_execution(raw, len);
    }
}

if constexpr شاخیں مکمل طور پر compile time پر حل ہوتی ہیں—تیار شدہ مشین کوڈ میں صرف متعلقہ راستہ صفر branching اووور ہیڈ کے ساتھ شامل ہوتا ہے۔ یہ تکنیک، link-time optimization (LTO) کے ساتھ ملی، ایسے بائنریز پیدا کرتی ہے جہاں پروٹوکول پارسنگ بنیادی طور پر میموری ریڈز کے سیدھی-لائن سلسلے میں کھلی ہوتی ہے۔

جدید C++20/23 خصوصیات جیسے consteval، concepts، اور compile-time containers اس کو مزید آگے دھکیلتی ہیں، پوری آرڈر ویریفیکیشن پائپ لائنز کو compile time پر کمپیوٹ کرنے کی اجازت دیتی ہیں۔

کیش-دوستانہ ڈیزائن اور کرنل بائی پاس نیٹ ورکنگ

سب-مائیکرو سیکنڈ لیٹنسیز پر، CPU کیش hierarchy آپ کا سب سے اہم آپٹیمائزیشن ٹارگٹ بن جاتا ہے۔ ایک مین میموری میں کیش مِس تقریباً 100 نینو سیکنڈز خرچ کرتا ہے—یہ ایک صدی ہے جب آپ کا کل لیٹنسی بجٹ 500ns ہو۔

// BAD: Array of Pointers (AoP) — کیش-دشمن
std::vector<std::unique_ptr<Order>> orders;  // ہر رسائی = pointer chase + کیش مِس

// GOOD: Struct of Arrays (SoA) — کیش-دوستانہ
struct OrderBook {
    std::vector<double> prices;      // میموری میں مسلسل
    std::vector<uint32_t> quantities; // میموری میں مسلسل
    std::vector<uint64_t> order_ids;  // میموری میں مسلسل
};
// prices کو iterate کرنا = مسلسل کیش لائن ریڈز = تیز

نیٹ ورکنگ کے لیے، Linux کرنل کا TCP/IP اسٹیک فی پیکٹ 5-15 مائیکرو سیکنڈز لیٹنسی شامل کرتا ہے۔ ٹریڈنگ فرمیں اسے مکمل طور پر بائی پاس کرتی ہیں:

  • DPDK (Data Plane Development Kit) — یوزر اسپیس سے براہِ راست NIC کو پول کرتا ہے، کرنل کو بائی پاس کرتے ہوئے۔ سب-مائیکرو سیکنڈ پیکٹ پروسیسنگ حاصل کرتا ہے۔
  • Solarflare OpenOnload — ایک مانوس socket API کے ساتھ کرنل بائی پاس۔ ایکویٹیز اور فیوچرز ٹریڈنگ میں بڑے پیمانے پر استعمال ہوتا ہے۔
  • FPGA NICs — Xilinx Alveo اور مماثل کارڈز ہارڈویئر میں مارکیٹ ڈیٹا پارس اور آرڈرز پیدا کر سکتے ہیں، نینو سیکنڈ-سطح لیٹنسیز حاصل کرتے ہوئے۔

وکندریت ایکسچینجز بھی ان اصولوں سے فائدہ اٹھاتی ہیں۔ Hyperliquid کی L1 چین—جو GaiaEx جیسے پلیٹ فارمز کو طاقت دیتی ہے—کو زیادہ-تھرو پٹ، کم-لیٹنسی consensus کے ساتھ ڈیزائن کیا گیا تھا، اور اس سے منسلک مارکیٹ میکرز آپٹیمائزڈ C++ کلائنٹس استعمال کرتے ہیں تاکہ قیمت اپڈیٹ موصول ہونے اور آرڈر پیش کرنے کے درمیان وقت کم سے کم کیا جا سکے۔

ایکسچینج میچنگ انجنز کیسے بنائے جاتے ہیں

ہر ایکسچینج کے قلب میں میچنگ انجن بیٹھا ہوتا ہے—وہ جز جو خرید اور فروخت آرڈرز کو جوڑتا ہے۔ یہ پوری مالیات میں سب سے زیادہ لیٹنسی-حساس سافٹ ویئر ہے، اور یہ تقریباً ہمیشہ C++ میں لکھا جاتا ہے۔

ایک آسان کیا گیا میچنگ انجن آرکیٹیکچر:

class MatchingEngine {
    // فی انسٹرومنٹ ایک آرڈر بک
    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);      // ٹریڈنگ فرمز کو
            update_market_data(fill);     // ڈیٹا فیڈ کو
        }

        if (order.remaining_qty > 0) {
            book.insert(order);           // بک پر باقی رہتا ہے
        }
    }
};

پروڈکشن میچنگ انجنز اس بنیادی ساخت سے بہت آگے آپٹیمائز کرتے ہیں:

  • قیمت-وقت ترجیح — ایک ہی قیمت پر آرڈرز آنے کی ترتیب میں پُر کیے جاتے ہیں، نینو سیکنڈ ٹائم اسٹیمپس سے ٹریک کیے جاتے ہیں۔
  • پیشگی-مخصوص آرڈر پولز — میچنگ کے دوران کوئی ہیپ allocation نہیں۔ آرڈرز مقررہ پولز سے دوبارہ استعمال ہوتے ہیں۔
  • Lockless ڈیزائن — ہر انسٹرومنٹ کی بک ایک مخصوص core پر چلتی ہے۔ کوئی cross-book locking درکار نہیں۔
  • قطعی ری پلے — ہر آرڈر اور میچ ریگولیٹری تعمیل اور ڈیزاسٹر ریکوری کے لیے مستقل اسٹوریج میں journaled کیا جاتا ہے۔

اس سطح پر سسٹمز بنانا وہ جگہ ہے جہاں C++ حقیقتاً چمکتا ہے۔ کوئی دوسری mainstream زبان آپ کو یکبارگی میموری لے آؤٹ، thread شیڈولنگ، کیش رویے، اور نیٹ ورک I/O پر کنٹرول نہیں دیتی—انتہائی-کم-لیٹنسی انجینئرنگ کے چار ستون۔ چاہے آپ اگلا ایکسچینج بنا رہے ہوں، ایک مارکیٹ میکر کے طور پر اس سے کنیکٹ ہو رہے ہوں، یا ایک پراپ فرم میں نفاذ آپٹیمائز کر رہے ہوں، C++ بلا مقابلہ بادشاہ ہے۔