← Kembali ke artikel
March 27, 2026
Bacaan 5 minit

ZigBolt: Mengapa Kami Membina Aeron Kami Sendiri dalam Zig dan Mencapai 20 Nanosaat Per Mesej

#zigbolt
#zig
#hft
#kependaman-rendah
#penghantar-mesej
#aeron
#ipc
#sumber-terbuka

ZigBolt — penghantar mesej kependaman ultra-rendah Penimbal gelang bebas-kunci, codec sifar-salinan, kluster Raft — semua dalam Zig tulen, semua sumber terbuka.

Jika anda bekerja dalam dagangan algoritmik atau pembuat pasaran, anda tahu harga setiap mikrosaat. Satu peralihan konteks tambahan — dan pesanan anda tiba di tempat kedua. Satu jeda JVM GC — dan pembuat pasaran di sebelah lain sudah mengemas kini petikan. Dalam dunia di mana wang diukur dalam nanosaat, infrastruktur penghantar mesej bukan paip membosankan antara perkhidmatan — ia adalah kelebihan daya saing.

Kami membina ZigBolt — sistem penghantar mesej untuk dagangan frekuensi tinggi yang ditulis dalam Zig. Dari awal. Tiada JVM, tiada pengumpul sampah, tiada Media Driver, tiada konfigurasi XML. Dan kami mendapat kependaman p50 20 nanosaat pada penimbal gelang SPSC dan 30 nanosaat pada IPC melalui memori bersama.

Artikel ini merangkumi mengapa kami memerlukannya, bagaimana ia berfungsi di dalam, dan mengapa Zig.


TL;DR

  • ZigBolt — sistem penghantar mesej sumber terbuka (MIT) untuk HFT dalam Zig tulen
  • 20 ns p50 pada SPSC, 30 ns p50 pada IPC — lebih pantas daripada nombor yang diterbitkan Aeron
  • Codec sifar-salinan berjalan pada 0 ns (penjanaan masa-kompil, masa jalan hanyalah penghantaran penuding)
  • Tiada GC, tiada JVM, tiada Media Driver — perpustakaan tertanam terus ke dalam aplikasi anda
  • Kluster Raft, arkib, penjujukan — semua disertakan
  • Pengikatan FFI untuk Rust, Python, Go, TypeScript, C — bekerja dalam bahasa yang anda suka

Masalah: Mengapa Aeron Bagus Tetapi Tidak Mencukupi

Aeron dari Real Logic adalah piawaian de facto untuk penghantar mesej kependaman rendah di pasaran modal. Berpuluh-puluh firma HFT menggunakannya, ia telah teruji dalam pertempuran, dan mempunyai seni bina yang sangat baik. Tetapi Aeron mempunyai masalah asas, dan namanya adalah JVM.

Titik Selamat JVM: Musuh Tidak Kelihatan

Walaupun anda meletakkan semua data dengan teliti dalam memori off-heap, walaupun anda melumpuhkan ergonomik GC dan menetapkan GuaranteedSafepointInterval=300000 — JVM masih kadangkala menghentikan semua urutan di titik selamat. Ini bukan pepijat, ini adalah keputusan seni bina: JVM memerlukan titik selamat untuk penyahoptimuman, penguncian berat sebelah, dan penjelajahan tindanan.

Dalam amalan ia kelihatan seperti ini: urutan anda menghantar mesej pada p50 = 200 ns, dan tiba-tiba p99.9 melonjak ke 50 us. Tanpa sebab yang jelas. Kerana salah satu urutan JVM memutuskan sudah tiba masanya.

Media Driver: Lompatan Tambahan

Aeron berfungsi melalui Media Driver — proses berasingan (atau JVM tertanam) yang menghalakan mesej antara penerbit dan pelanggan melalui memori bersama. Ini memberikan pengasingan yang baik tetapi menambah sekurang-kurangnya satu lompatan tambahan:

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

Setiap lompatan bermakna nanosaat tambahan, ketinggalan cache tambahan, ketidakpastian tambahan.

SBE: Langkah Binaan Berasingan

Simple Binary Encoding — codec FIX standard untuk mesej kewangan. Dalam ekosistem Aeron, ia adalah utiliti Java berasingan yang menjana kod dari skema XML. Kebergantungan berasingan, langkah binaan berasingan, set masalah berasingan.


Penyelesaian: ZigBolt

Kami bertanya kepada diri sendiri: bagaimana jika kami mengambil idea terbaik Aeron — log tiga-penimbal, penimbal gelang bebas-kunci, kluster Raft — dan melaksanakannya dalam bahasa yang:

  1. Tidak mempunyai overhed masa jalan (tiada GC, tiada titik selamat)
  2. Membolehkan penjanaan kod pada masa kompil (comptime)
  3. Berintegrasi dengan mudah dengan perpustakaan C (DPDK, io_uring)
  4. Mengkompil kepada binari ~100 KB

Bahasa itu adalah Zig.


Seni Bina

┌─────────────────────────────────────────────────────────┐
│  Publisher/Subscriber API (typed generic wrappers)       │
├─────────────────────────────────────────────────────────┤
│  Transport Layer (channel factory & lifecycle)            │
├─────────────────────────────────────────────────────────┤
│  IPC Channel (shared memory)  │ UDP Channel (network)    │
├─────────────────────────────────────────────────────────┤
│  WireCodec (comptime, zero-copy) │ SBE Encoder/Decoder   │
├─────────────────────────────────────────────────────────┤
│  Ring Buffers (SPSC/MPSC) │ LogBuffer (triple-buffered)  │
├─────────────────────────────────────────────────────────┤
│  Archive (replay) │ Sequencer (total order) │ Raft (HA)  │
└─────────────────────────────────────────────────────────┘

Tujuh lapisan, setiap satunya boleh digunakan secara bebas. Hanya perlu penimbal gelang SPSC untuk IPC antara dua proses? Ambil sahaja. Perlu kluster penuh dengan konsensus Raft dan pengarkiban? Juga ada.


Penanda Aras: Nombor, Bukan Kata-kata

Keputusan Penanda Aras ZigBolt

Keputusan penanda aras sebenar melebihi 10 juta lelaran (Apple Silicon / macOS):

Penimbal Gelang SPSC

Saiz Mesej p50 p99 p99.9 Daya Pemprosesan
8 bait 20 ns 30 ns 120 ns 42.8M msg/s
32 bait 30 ns 50 ns 150 ns 28.5M msg/s
64 bait 50 ns 60 ns 320 ns 17.6M msg/s
256 bait 30 ns 50 ns 50 ns 29.5M msg/s

Saluran IPC (memori bersama)

Saiz Mesej p50 p99 p99.9 Daya Pemprosesan
64 bait 30 ns 40 ns 40 ns 35.7M msg/s
256 bait 40 ns 40 ns 170 ns 27.4M msg/s
1024 bait 90 ns 260 ns 900 ns 9.9M msg/s

LogBuffer (tiga-penimbal gaya Aeron)

Saiz Mesej p50 p99 p99.9 Daya Pemprosesan
32 bait 30 ns 40 ns 320 ns 33.6M msg/s
64 bait 30 ns 30 ns 160 ns 38.0M msg/s
256 bait 30 ns 40 ns 60 ns 31.1M msg/s

WireCodec (sifar-salinan comptime)

Operasi Kependaman Daya Pemprosesan
Enkod (32 bait) 0 ns memcpy diselaraskan
Dekod (32 bait) ~0.4 ns 2.7 bilion msg/s

Ya, anda membaca dengan betul: pengekodan mengambil sifar nanosaat. Kerana WireCodec(T) mengesahkan struct pada masa kompil dan menukar enkod/dekod kepada @memcpy biasa atau penghantaran penuding. Overhed masa jalan = sifar.

Sebagai perbandingan: Aeron mendakwa IPC RTT (perjalanan pusingan) sekitar ~250 ns. Kependaman sehala kami adalah 30 ns. Walaupun mengira perjalanan pusingan, kami 4x lebih pantas.


Cara Ia Berfungsi Di Dalam

SPSC Bebas-Kunci: Kesederhanaan sebagai Kebajikan

Penimbal Gelang SPSC

Penimbal gelang pengeluar-tunggal pengguna-tunggal adalah struktur data bebas-kunci yang paling mudah dan paling pantas. Penulis menggerakkan head, pembaca menggerakkan tail, tiada CAS diperlukan — atomik perolehan/pelepasan mencukupi.

Helah utama adalah padding baris cache. Jika head dan tail berada dalam baris cache yang sama, setiap kemas kini kepada satu pembilang membatalkan cache untuk teras lain (perkongsian palsu). Pembaikannya:

// Head (kedudukan tulis) — pada baris cache sendiri
head: std.atomic.Value(usize) align(128) = .init(0),

// Padding 128 bait — pengasingan terjamin
_pad0: [128 - @sizeOf(std.atomic.Value(usize))]u8 = .{0} ** ...,

// Tail (kedudukan baca) — pada baris cache sendiri
tail: std.atomic.Value(usize) align(128) = .init(0),

128 bait, bukan 64 — kerana pada Apple Silicon (dan banyak cip ARM) pengambil awal perkakasan boleh bekerja dengan pasangan baris cache. Kami bermain selamat.

WireCodec: Comptime Berbanding Penjanaan Kod

WireCodec — codec masa-kompil

Dalam dunia Java/C++, codec binari memerlukan langkah berasingan: tulis skema, jalankan penjana kod, dapatkan kod, kompil. Dalam Zig, semua ini berlaku pada masa kompil:

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

const Codec = WireCodec(TickMsg);

// Enkod — hanya memcpy 32-bait. Diselaraskan ke 1-2 arahan.
Codec.encode(&msg, buf[0..Codec.wire_size]);

// Dekod — penghantaran penuding. Sifar salinan.
const tick = Codec.decode(buf[0..Codec.wire_size]);

Pengkompil Zig mengesahkan pada comptime:

  • Struct adalah packed (tiada lubang padding)
  • Saiz adalah gandaan 8 bait (penjajaran untuk SIMD)
  • Semua medan adalah jenis primitif

Jika ada yang salah — ralat kompil, bukan pengecualian masa jalan pada pukul 3 pagi dalam pengeluaran.

IPC melalui Memori Bersama

Dua proses memetakan fail yang sama dalam /dev/shm. Penerbit menulis ke penimbal gelang, pelanggan membaca. Tiada soket, tiada panggilan sistem pada laluan panas:

// Penerbit
const channel = try IpcChannel.create("/market-data", .{
    .term_length = 1024 * 1024, // 1 MB
});
channel.publish(&msg_bytes, msg_type_id);

// Pelanggan (proses lain)
const channel = try IpcChannel.open("/market-data", .{
    .term_length = 1024 * 1024,
});
const count = channel.poll(handler_fn, 10);

Keseluruhan laluan dari publish() ke pemanggilan handler_fn dalam pelanggan — 30 nanosaat untuk mesej 64-bait.

Kebolehpercayaan Berasaskan NAK untuk UDP

Untuk pengangkutan rangkaian, ZigBolt menggunakan penghantaran semula yang didorong penerima. Penerima menjejaki jurang dalam nombor urutan melalui bitmap dan menghantar NAK (pengakuan negatif) kepada penghantar. Ditambah kawalan kesesakan AIMD — permulaan perlahan gaya TCP dan pengelakan kesesakan — untuk mengelakkan banjir rangkaian.

Kluster Raft: Apabila Anda Memerlukan Konsistensi

Untuk kes di mana kehilangan mesej tidak boleh diterima (contohnya, enjin padanan), ZigBolt menyertakan konsensus Raft penuh:

  • Pemilihan pemimpin dengan tamat masa yang boleh dikonfigurasi (150-300 ms)
  • Replikasi log — pemimpin mereplikasi setiap mesej kepada pengikut
  • Log tulis-awal dengan pengesahan CRC32 dan pemulihan kerosakan
  • Syot kilat — supaya WAL tidak berkembang selama-lamanya

Arkib: Rekod dan Putar Semula

Semua mesej boleh direkodkan ke arkib tersegmen di cakera. Kemudian — putar semula dari sebarang kedudukan mengikut masa atau nombor urutan. Pemampatan gaya LZ4 terbina dalam tanpa kebergantungan luaran. Indeks jarang untuk carian pantas dalam segmen.

Penjujukan Pesanan-Total

Untuk pembuat pasaran merentasi pelbagai tempat, adalah kritikal bahawa semua peristiwa mempunyai perintah global. Penjujukan mengambil N aliran input dan menggabungkannya menjadi satu, memberikan nombor urutan yang meningkat secara monoton. Setiap peserta melihat urutan peristiwa yang sama.


Mengapa Zig, Bukan Rust/C/C++?

Kami memilih antara empat calon. Berikut adalah perbandingan jujur:

Kriteria Zig C/C++ Rust Java (Aeron)
Overhed GC / masa jalan Tiada Tiada Tiada Titik selamat JVM, GC
Penjanaan kod comptime Asli Makro/templat makro proc Tiada
Interop C (DPDK, io_uring) @cImport mudah Asli FFI/bindgen Overhed JNI
SIMD @Vector, terbina dalam Intrinsik packed_simd (tidak stabil) Petunjuk vektorisasi
Penyusunan silang Terbina dalam Neraka CMake cargo target N/A
Masa binaan Saat Minit (C++) Minit Saat + permulaan JVM
Aliran kawalan tersembunyi Tiada Pengecualian, hantaran tersirat Panik dalam unwrap Pengecualian

Zig memberikan kami kombinasi unik: prestasi peringkat C + keselamatan semasa pembangunan + metaprograman comptime (codec, jadual carian, mesin keadaan protokol — semua dijana pada masa kompil) + integrasi mudah dengan DPDK, liburing, ef_vi melalui @cImport.

Dan binari Zig bersaiz ~100 KB. Berbanding 20+ MB untuk penyelesaian berasaskan JVM.


Pengikatan: Bekerja dalam Bahasa Anda

ZigBolt mengkompil kepada perpustakaan bersama dengan C-ABI, dan kami mempunyai pengikatan siap untuk lima bahasa:

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();

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)

Ditambah Go dan C biasa. Saluran memori bersama yang sama boleh diakses dari semua bahasa serentak — penerbit dalam Zig, pelanggan dalam Python, pemantauan dalam Go. Mereka semua membaca kawasan mmap yang sama.


Codec SBE: Mesej Serasi FIX

Untuk protokol kewangan, ZigBolt menyertakan codec SBE (Simple Binary Encoding) penuh dengan skema masa-kompil. Jenis mesej terbina dalam:

  • NewOrderSingle — penyerahan pesanan
  • ExecutionReport — laporan pelaksanaan
  • MarketDataIncrementalRefresh — kemas kini data pasaran tambahan
  • MassQuote — petikan massa
  • Heartbeat — semakan kesambungan
  • Logon — pengesahan

Tiada penjana kod luaran, tiada XML. Semua diterangkan dengan struct Zig dan disahkan pada masa kompil.


Protokol Wire: Keserasian Aeron

ZigBolt melaksanakan flyweight protokol wire serasi Aeron:

  • DataHeaderFlyweight — bingkai data
  • StatusMessage — kawalan aliran
  • NAK — pengakuan negatif
  • Setup, RTT, Error — bingkai perkhidmatan

Ini bermakna ZigBolt boleh wujud bersama dengan infrastruktur Aeron yang sedia ada. Penghijrahan tidak perlu menjadi peledak besar.


Apa Seterusnya

ZigBolt kini berada pada versi 0.2.1. Teras adalah stabil, penanda aras boleh dihasilkan semula, pengikatan berfungsi. Akan datang tidak lama lagi:

  • Backend io_uring — pengangkutan rangkaian sifar-salinan pada Linux 6.0+ (IORING_OP_SEND_ZC)
  • DPDK / AF_XDP — pintasan kernel untuk bila setiap mikrosaat dikira
  • Multi-Raft — pecahan mengikut instrumen/strategi
  • Arkib lajur — integrasi Apache Arrow/Parquet untuk analitik
  • Sokongan Hugepage — hugepage 2MB/1GB yang dipra-lalai untuk meminimumkan kesilapan TLB

Cuba Ia

Bina dari sumber (zig build), jalankan penanda aras (zig build bench), sambung melalui FFI dari mana-mana bahasa. Jika anda mempunyai Zig 0.15.1 dan beberapa minit — cuba penanda aras ping-pong dan bandingkan dengan penyelesaian semasa anda.


Pautan:


Petikan

@software{soloviov2026zigbolt,
  author = {Soloviov, Eugen},
  title = {ZigBolt: Why We Built Our Own Aeron in Zig and Hit 20 Nanoseconds Per Message},
  year = {2026},
  url = {https://marketmaker.cc/ms/blog/post/zigbolt-zig-messaging-hft},
  version = {0.2.1},
  description = {Bagaimana dan mengapa kami membina sistem penghantar mesej ultra-rendah kependaman untuk HFT dari awal dalam Zig. Tiada JVM, tiada GC, tiada kejutan.}
}
Penafian: Maklumat yang disediakan dalam artikel ini adalah untuk tujuan pendidikan dan maklumat sahaja dan bukan merupakan nasihat kewangan, pelaburan, atau dagangan. Dagangan mata wang kripto melibatkan risiko kerugian yang ketara.

Pengarang

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Kekal Mendahului Pasaran

Langgan surat berita kami untuk pandangan dagangan AI eksklusif, analisis pasaran, dan kemas kini platform.

Kami menghormati privasi anda. Berhenti melanggan pada bila-bila masa.