← العودة إلى قائمة المقالات
March 27, 2026
5 دقائق للقراءة

ZigBolt: لماذا بنينا نظام Aeron الخاص بنا بلغة Zig وحققنا 20 نانوثانية لكل رسالة

ZigBolt: لماذا بنينا نظام Aeron الخاص بنا بلغة Zig وحققنا 20 نانوثانية لكل رسالة
#zigbolt
#zig
#تداول عالي التردد
#زمن استجابة منخفض
#نظام رسائل
#aeron
#ipc
#مفتوح المصدر

ZigBolt — نظام رسائل فائق السرعة مخازن حلقية بدون أقفال، مُرمِّزات بدون نسخ، مجموعة Raft — الكل بلغة Zig الخالصة، الكل مفتوح المصدر.

إذا كنت تعمل في التداول الخوارزمي أو صناعة السوق، فأنت تعرف قيمة كل ميكروثانية. تبديل سياق واحد إضافي — وأمر التداول الخاص بك يصل متأخراً. توقف واحد لجمع القمامة في JVM — وصانع السوق على الطرف الآخر قد حدّث أسعاره بالفعل. في عالم تُقاس فيه الأرباح بالنانوثانية، البنية التحتية للرسائل ليست أنبوباً مملاً بين الخدمات، بل هي ميزة تنافسية جوهرية.

بنينا ZigBolt — نظام رسائل للتداول عالي التردد بلغة Zig. من الصفر. بدون JVM، بدون جامع قمامة، بدون Media Driver، بدون ملفات تكوين XML. وحققنا زمن استجابة p50 يبلغ 20 نانوثانية على مخزن SPSC الحلقي و30 نانوثانية على IPC عبر الذاكرة المشتركة.

هذه المقالة تشرح لماذا كان هذا ضرورياً، وكيف يعمل من الداخل، ولماذا اخترنا Zig.


ملخص

  • ZigBolt — نظام رسائل مفتوح المصدر (MIT) للتداول عالي التردد بلغة Zig الخالصة
  • p50: 20 نانوثانية على SPSC، p50: 30 نانوثانية على IPC — أسرع من المواصفات المعلنة لـ Aeron
  • مُرمِّز بدون نسخ يعمل بـ 0 نانوثانية (توليد الكود وقت الترجمة، وقت التشغيل مجرد تحويل مؤشر)
  • بدون GC، بدون JVM، بدون Media Driver — المكتبة تُدمج مباشرة في التطبيق
  • مجموعة Raft، أرشيف، مُرتِّب — كل شيء مضمّن
  • روابط FFI لـ Rust وPython وGo وTypeScript وC — اعمل بلغتك المفضلة

المشكلة: Aeron ممتاز، لكنه غير كافٍ

Aeron من Real Logic هو المعيار الفعلي لنظام الرسائل منخفض زمن الاستجابة في أسواق رأس المال. تستخدمه عشرات شركات التداول عالي التردد، وقد أثبت كفاءته في الميدان، ويتمتع بمعمارية ممتازة. لكن لدى Aeron مشكلة جوهرية — JVM.

نقاط الأمان في JVM: العدو الخفي

حتى لو وضعت جميع البيانات بعناية في ذاكرة خارج الكومة، حتى لو عطّلت التعديل التلقائي لجمع القمامة وضبطت GuaranteedSafepointInterval=300000، فإن JVM لا تزال توقف جميع الخيوط عند نقاط الأمان من وقت لآخر. هذا ليس خطأ برمجياً، بل قرار معماري: تحتاج JVM إلى نقاط الأمان لإلغاء التحسين، والقفل المنحاز، واستعراض المكدس.

عملياً يبدو الأمر هكذا: خيطك يرسل رسائل بـ p50 = 200 نانوثانية، وفجأة يقفز p99.9 إلى 50 ميكروثانية. بدون سبب واضح. لأن أحد خيوط JVM قرر أن الوقت قد حان لنقطة أمان.

Media Driver: قفزة إضافية

يعمل Aeron عبر Media Driver — عملية منفصلة (أو JVM مدمجة) تُوجّه الرسائل بين الناشر والمشترك عبر الذاكرة المشتركة. هذا يوفر عزلاً جميلاً، لكنه يضيف قفزة إضافية واحدة على الأقل:

Aeron:    App → shm → Media Driver → shm → socket → NIC
ZigBolt:  App → ring buffer → io_uring → NIC

كل قفزة تعني نانوثوانٍ إضافية، وإخفاقات ذاكرة مخبئية إضافية، وعدم قابلية للتنبؤ.

SBE: خطوة بناء منفصلة

Simple Binary Encoding هو مُرمِّز FIX القياسي للرسائل المالية. في منظومة Aeron، هو أداة Java منفصلة تولّد الكود من مخططات XML. اعتماد منفصل، خطوة بناء منفصلة، مجموعة مشاكل منفصلة.


الحل: ZigBolt

سألنا أنفسنا: ماذا لو أخذنا أفضل أفكار Aeron — السجل ثلاثي التخزين المؤقت، المخازن الحلقية بدون أقفال، مجموعة Raft — ونفّذناها بلغة:

  1. ليس لها عبء وقت تشغيل (لا GC، لا نقاط أمان)
  2. تسمح بتوليد الكود وقت الترجمة (comptime)
  3. تتكامل بسهولة مع مكتبات C (مثل DPDK وio_uring)
  4. تُنتج ملفاً تنفيذياً بحجم ~100 كيلوبايت

تلك اللغة هي Zig.


المعمارية

┌─────────────────────────────────────────────────────────┐
│  Publisher/Subscriber API (أغلفة مُنمَّطة)               │
├─────────────────────────────────────────────────────────┤
│  Transport Layer (مصنع القنوات، دورة الحياة)              │
├─────────────────────────────────────────────────────────┤
│  IPC Channel (ذاكرة مشتركة)  │ UDP Channel (شبكة)       │
├─────────────────────────────────────────────────────────┤
│  WireCodec (comptime بدون نسخ) │ SBE Encoder/Decoder    │
├─────────────────────────────────────────────────────────┤
│  Ring Buffers (SPSC/MPSC) │ LogBuffer (ثلاثي التخزين)    │
├─────────────────────────────────────────────────────────┤
│  Archive (إعادة تشغيل) │ Sequencer (ترتيب كلي) │ Raft (HA) │
└─────────────────────────────────────────────────────────┘

سبع طبقات، كل منها يمكن استخدامها بشكل مستقل. تحتاج فقط مخزناً حلقياً SPSC للـ IPC بين عمليتين؟ خذه. تحتاج مجموعة كاملة مع إجماع Raft وأرشيف؟ موجود أيضاً.


اختبارات الأداء: أرقام لا كلام

نتائج اختبارات أداء ZigBolt

هذه النتائج الفعلية على 10 ملايين تكرار (Apple Silicon / macOS):

مخزن SPSC الحلقي

حجم الرسالة p50 p99 p99.9 الإنتاجية
8 بايت 20 نانوثانية 30 نانوثانية 120 نانوثانية 42.8M msg/s
32 بايت 30 نانوثانية 50 نانوثانية 150 نانوثانية 28.5M msg/s
64 بايت 50 نانوثانية 60 نانوثانية 320 نانوثانية 17.6M msg/s
256 بايت 30 نانوثانية 50 نانوثانية 50 نانوثانية 29.5M msg/s

قناة IPC (ذاكرة مشتركة)

حجم الرسالة p50 p99 p99.9 الإنتاجية
64 بايت 30 نانوثانية 40 نانوثانية 40 نانوثانية 35.7M msg/s
256 بايت 40 نانوثانية 40 نانوثانية 170 نانوثانية 27.4M msg/s
1024 بايت 90 نانوثانية 260 نانوثانية 900 نانوثانية 9.9M msg/s

LogBuffer (ثلاثي التخزين على نمط Aeron)

حجم الرسالة p50 p99 p99.9 الإنتاجية
32 بايت 30 نانوثانية 40 نانوثانية 320 نانوثانية 33.6M msg/s
64 بايت 30 نانوثانية 30 نانوثانية 160 نانوثانية 38.0M msg/s
256 بايت 30 نانوثانية 40 نانوثانية 60 نانوثانية 31.1M msg/s

WireCodec (comptime بدون نسخ)

العملية زمن الاستجابة الإنتاجية
ترميز (32 بايت) 0 نانوثانية ∞ (memcpy مضمّن)
فك الترميز (32 بايت) ~0.4 نانوثانية 2.7 مليار msg/s

نعم، قرأت ذلك بشكل صحيح: الترميز يستغرق صفر نانوثانية. لأن WireCodec(T) يتحقق من البنية وقت الترجمة ويحوّل عمليات الترميز/فك الترميز إلى @memcpy عادي أو تحويل مؤشر. عبء وقت التشغيل = صفر.

للمقارنة: تعلن Aeron عن IPC RTT (ذهاباً وإياباً) بحوالي 250 نانوثانية. زمن الاستجابة أحادي الاتجاه لدينا هو 30 نانوثانية. حتى لو حسبنا الذهاب والإياب، نحن أسرع بـ 4 مرات.


كيف يعمل من الداخل

SPSC بدون أقفال: البساطة كفضيلة

مخزن SPSC الحلقي

المخزن الحلقي أحادي المنتج وأحادي المستهلك هو أبسط وأسرع بنية بيانات بدون أقفال. الكاتب يحرّك head، والقارئ يحرّك tail، ولا حاجة لـ CAS — العمليات الذرية acquire/release كافية.

الحيلة الأساسية هي حشو سطر الذاكرة المخبئية. إذا كان head وtail في نفس سطر الذاكرة المخبئية، فإن كل تحديث لأحد العدادين يُبطل ذاكرة النواة الأخرى (المشاركة الزائفة). الحل:

// Head (موضع الكتابة) — في سطر ذاكرة مخبئية خاص به
head: std.atomic.Value(usize) align(128) = .init(0),

// حشو 128 بايت — لضمان العزل
_pad0: [128 - @sizeOf(std.atomic.Value(usize))]u8 = .{0} ** ...,

// Tail (موضع القراءة) — في سطر ذاكرة مخبئية خاص به
tail: std.atomic.Value(usize) align(128) = .init(0),

128 بايت وليس 64 — لأنه في Apple Silicon (والعديد من معمارية ARM) قد يعمل الجلب المسبق العتادي بأزواج من سطور الذاكرة المخبئية. نحن نأخذ الاحتياط.

WireCodec: comptime بدلاً من توليد الكود

WireCodec — مُرمِّز وقت الترجمة

في عالم Java/C++، تحتاج مُرمِّزات ثنائية إلى خطوة منفصلة: كتابة المخطط ← تشغيل مولّد الكود ← الحصول على الكود ← الترجمة. في Zig، كل هذا يتم وقت الترجمة:

const TickMsg = packed struct {
    symbol_id: u32,
    price: i64,
    quantity: u32,
    side: u8,
    _reserved: [3]u8,
    timestamp: u64,
};

const Codec = WireCodec(TickMsg);

// الترميز — مجرد نسخ 32 بايت. يتحول إلى 1-2 تعليمة.
Codec.encode(&msg, buf[0..Codec.wire_size]);

// فك الترميز — تحويل مؤشر. صفر نسخ.
const tick = Codec.decode(buf[0..Codec.wire_size]);

مترجم Zig يتحقق في مرحلة comptime من:

  • البنية packed (لا ثقوب حشو)
  • الحجم من مضاعفات 8 بايت (محاذاة SIMD)
  • جميع الحقول أنواع بدائية

إذا كان هناك خطأ — يظهر كخطأ ترجمة، وليس استثناء وقت تشغيل في الثالثة صباحاً على بيئة الإنتاج.

IPC عبر الذاكرة المشتركة

تقوم عمليتان بتعيين نفس الملف في /dev/shm. الناشر يكتب في المخزن الحلقي، والمشترك يقرأ. لا مقابس، لا استدعاءات نظام في المسار الحرج:

// Publisher
const channel = try IpcChannel.create("/market-data", .{
    .term_length = 1024 * 1024, // 1 ميغابايت
});
channel.publish(&msg_bytes, msg_type_id);

// Subscriber (عملية أخرى)
const channel = try IpcChannel.open("/market-data", .{
    .term_length = 1024 * 1024,
});
const count = channel.poll(handler_fn, 10);

المسار الكامل من publish() إلى استدعاء handler_fn في المشترك — 30 نانوثانية لرسالة بحجم 64 بايت.

موثوقية UDP المبنية على NAK

للنقل الشبكي، يستخدم ZigBolt إعادة الإرسال بقيادة المستقبِل. يتتبع المستقبِل الفجوات في أرقام التسلسل عبر خريطة بتات ويرسل NAK (إقرار سلبي) إلى المرسل. بالإضافة إلى التحكم في الازدحام AIMD — بدء بطيء وتجنب ازدحام شبيه بـ TCP — لمنع إغراق الشبكة.

مجموعة Raft: عندما تكون الاتساقية ضرورية

للحالات التي لا يمكن فيها فقدان رسالة (مثل محرك المطابقة)، يتضمن ZigBolt إجماع Raft كاملاً:

  • انتخاب القائد بمهلة قابلة للتكوين (150-300 مللي ثانية)
  • نسخ السجل — القائد ينسخ كل رسالة إلى التابعين
  • سجل الكتابة المسبقة (WAL) مع التحقق بـ CRC32 واستعادة بعد الأعطال
  • لقطات — لمنع نمو WAL بلا حدود

الأرشيف: التسجيل وإعادة التشغيل

يمكن تسجيل جميع الرسائل في أرشيف مُجزّأ على القرص. ثم إعادة التشغيل من أي موضع بحسب الوقت أو رقم التسلسل. ضغط مدمج بأسلوب LZ4 بدون اعتماديات خارجية. فهرس متناثر للبحث السريع داخل الأجزاء.

مُرتِّب الترتيب الكلي

لصناعة السوق على عدة بورصات، يجب أن يكون لجميع الأحداث ترتيب عالمي. يأخذ المُرتِّب N تدفقات إدخال ويدمجها في تدفق واحد، مُعيّناً أرقام تسلسل متزايدة رتابياً. كل مشارك يرى نفس تسلسل الأحداث.


لماذا Zig وليس Rust/C/C++؟

قارنّا بين أربعة مرشحين. إليك جدول المقارنة الموضوعي:

المعيار Zig C/C++ Rust Java (Aeron)
GC / عبء وقت التشغيل لا يوجد لا يوجد لا يوجد نقاط أمان JVM، GC
توليد كود comptime أصلي ماكرو/قوالب proc macros لا يوجد
التوافق مع C (مثل DPDK وio_uring) @cImport بسيط أصلي FFI/bindgen عبء JNI
SIMD @Vector مدمج Intrinsics packed_simd (غير مستقر) تلميحات تسريع شعاعي
الترجمة المتقاطعة مدمجة جحيم CMake cargo target غير متاح
وقت البناء ثوانٍ دقائق (C++) دقائق ثوانٍ + بدء JVM
تدفق تحكم مخفي لا يوجد استثناءات، تحويلات ضمنية panic في unwrap استثناءات

قدّم Zig مزيجاً فريداً: أداء C + أمان أثناء التطوير + برمجة وصفية comptime (المُرمِّزات، جداول البحث، آلات حالة البروتوكول — كلها تُولَّد وقت الترجمة) + تكامل سلس مع DPDK وliburing وef_vi عبر @cImport.

وملف Zig التنفيذي يبلغ حوالي 100 كيلوبايت. مقابل أكثر من 20 ميغابايت للحلول المبنية على JVM. للنشر على الحافة والحاويات، هذا مهم.


الروابط اللغوية: اعمل بلغتك المفضلة

يُترجَم ZigBolt إلى مكتبة مشتركة بواجهة C-ABI، ولدينا روابط جاهزة لخمس لغات:

TypeScript / Node.js

import { IpcChannel } from "@zigbolt/node";

const channel = IpcChannel.create({
  name: "/my-market-data",
  termLength: 1024 * 1024,
});

const msg = Buffer.from("BTC/USDT 42000.50", "utf-8");
channel.publish(msg, 1);

Rust

use zigbolt::IpcChannel;

let ch = IpcChannel::create("/my-channel", 64 * 1024).unwrap();
ch.publish(b"hello", 1).unwrap();

// Subscriber
let sub = IpcChannel::open("/my-channel", 64 * 1024).unwrap();
sub.poll(|data, msg_type_id| {
    println!("got {} bytes, type={}", data.len(), msg_type_id);
}, 10);

Python

from zigbolt import IpcChannel

ch = IpcChannel.create("/market-data", term_length=1024*1024)
ch.publish(b"tick data here", msg_type_id=1)

وهناك أيضاً روابط Go وC الخالصة. نفس قناة الذاكرة المشتركة يمكن الوصول إليها من جميع اللغات في نفس الوقت — الناشر بـ Zig، المشترك بـ Python، المراقبة بـ Go. الجميع يقرأ نفس منطقة mmap.


مُرمِّز SBE: رسائل متوافقة مع FIX

للبروتوكولات المالية، يتضمن ZigBolt مُرمِّز SBE (Simple Binary Encoding) كاملاً بمخططات وقت الترجمة. أنواع الرسائل المدمجة:

  • NewOrderSingle — إرسال أمر
  • ExecutionReport — تقرير التنفيذ
  • MarketDataIncrementalRefresh — تحديث تدريجي لبيانات السوق
  • MassQuote — عرض أسعار جماعي
  • Heartbeat — فحص الاتصال
  • Logon — المصادقة

لا حاجة لمولّد كود خارجي، لا حاجة لـ XML. كل شيء يُوصف ببُنى Zig ويُتحقَّق منه وقت الترجمة.


بروتوكول السلك: التوافق مع Aeron

يُنفّذ ZigBolt واجهات بروتوكول السلك المتوافقة مع Aeron:

  • DataHeaderFlyweight — إطارات البيانات
  • StatusMessage — التحكم في التدفق
  • NAK — الإقرار السلبي
  • Setup وRTT وError — إطارات التحكم

هذا يعني أن ZigBolt يمكنه العيش في نفس المنظومة مع بنية Aeron التحتية الحالية. لا يجب أن تكون الهجرة دفعة واحدة.


ما التالي

ZigBolt حالياً في الإصدار 0.2.1. النواة مستقرة، اختبارات الأداء قابلة للتكرار، والروابط تعمل. في الخطط القريبة:

  • خلفية io_uring — نقل شبكي بدون نسخ على Linux 6.0+ (IORING_OP_SEND_ZC)
  • DPDK / AF_XDP — تجاوز النواة للحالات التي تكون فيها كل ميكروثانية حاسمة
  • Multi-Raft — تجزئة حسب الأدوات المالية/الاستراتيجيات
  • أرشيف عمودي — تكامل مع Apache Arrow/Parquet للتحليلات
  • دعم الصفحات الضخمة — صفحات ضخمة مسبقة التخصيص بحجم 2 ميغابايت/1 غيغابايت لتقليل إخفاقات TLB

جرّبه

ابنِ من المصدر (zig build)، شغّل اختبارات الأداء (zig build bench)، واتصل عبر FFI من أي لغة. إذا كان لديك Zig 0.15.1 ودقيقتان — جرّب اختبار ping-pong وقارنه مع حلّك الحالي.


روابط:


الاستشهاد

@software{soloviov2026zigbolt,
  author = {Soloviov, Eugen},
  title = {ZigBolt: لماذا بنينا نظام Aeron الخاص بنا بلغة Zig وحققنا 20 نانوثانية لكل رسالة},
  year = {2026},
  url = {https://marketmaker.cc/ar/blog/post/zigbolt-zig-messaging-hft},
  version = {0.2.1},
  description = {كيف ولماذا بنينا من الصفر نظام رسائل فائق السرعة للتداول عالي التردد بلغة Zig. بدون JVM، بدون GC، بدون مفاجآت.}
}
blog.disclaimer

MarketMaker.cc Team

البحوث والاستراتيجيات الكمية

ناقش في تلغرام
Newsletter

ابقَ متقدماً على السوق

اشترك في نشرتنا الإخبارية للحصول على رؤى حصرية حول تداول الذكاء الاصطناعي وتحليلات السوق وتحديثات المنصة.

نحترم خصوصيتك. يمكنك إلغاء الاشتراك في أي وقت.