
高频低延迟量化交易在.NET里的真实门槛
高频交易拼的是微秒甚至纳秒。一笔订单从策略算出到交易所收到,中间任何一层软件栈的抖动都可能让套利机会变成亏损。.NET 平台做这件事,起步比 C++ 晚,但 .NET Core 之后运行时性能大幅提升,配合正确的技术选型,完全能覆盖国内商品期货和股票 Level-2 行情的低延迟场景。
问题在于,多数 .NET 开发者习惯用 Web 那套思路写交易系统:依赖注入、日志框架、HTTP 客户端、异步任务满天飞。这些在业务系统里是优点,在高频场景里全是延迟地雷。要跨过这道坎,得从内存、网络、操作系统、硬件四个层面重新理解 .NET 的边界。
内存管理是生死线
GC 模式必须换掉
默认的 workstation GC 对延迟极其不友好。跑高频策略,第一件事是把 runtimeconfig.json 里的 System.GC.Server 设为 true,同时开启 System.GC.Concurrent 为 false。后台 GC 线程会跟策略线程抢 CPU,关掉它。
更关键的是 GCLatencyMode。用 GCLatencyMode.SustainedLowLatency 能大幅减少第 2 代回收的停顿,但真正要求苛刻的场景得靠 TryStartNoGCRegion 在某段关键路径上完全禁掉 GC。代价是内存预分配要算得非常准,超了会直接抛异常。

结构体和栈分配
高频代码里不要出现 class。订单、行情 Tick、持仓这些数据结构全部用 readonly struct,字段按大小排序避免 padding。能用 stackalloc 就在栈上分配,别碰堆。Span<T> 和 Memory<T> 切分缓冲区,零拷贝解析二进制行情。
值类型数组 double[]、long[] 比 List<double> 快一个数量级,因为后者每次扩容都复制堆内存。行情缓冲区用环形数组,预分配固定大小,索引取模推进,永远不触发分配。
网络协议与数据链路
交易所协议要裸写
国内 CTP 期货接口有 .NET 封装,但封装层会引入额外延迟。自研时直接用 P/Invoke 调 CTP 的 C++ API,或者走交易所提供的二进制协议。行情用 UDP 组播接收,交易用 TCP 长连接。UDP 收包要在 socket 上设置 ReceiveBufferSize 到几兆,一次性读走多个包再解析,减少系统调用次数。
TCP 交易连接关掉 Nagle 算法,设置 NoDelay = true。每个订单包自己组帧,别用 JSON 或 protobuf 这种带解析开销的格式,直接拼二进制。
内核旁路与共享内存
要求再高一层,上 Solarflare 网卡配合 OpenOnload,把网络栈从内核移到用户态。.NET 通过 P/Invoke 调用 onload 的 socket 系列函数,绕过内核协议栈。行情和交易进程之间用共享内存通信,MemoryMappedFile 在 .NET 里就能做,写端和读端约定好环形缓冲区结构,中间没有任何锁。
CPU 亲和性与中断控制
高频策略进程要绑核。用 Process.ProcessorAffinity 把策略线程固定到某个物理核,行情接收线程固定到另一个核,避免操作系统调度器把线程在核间来回搬。绑核之后还要关掉该核上的中断,Linux 下改 /proc/irq/*/smp_affinity,Windows 下用 InterruptAffinityPolicy 工具。
超线程要关。BIOS 里禁用 Hyper-Threading,两个逻辑核共享物理核的 L1/L2 缓存,一个核跑行情一个核跑策略会互相踩缓存。NUMA 架构的服务器还要设置内存分配节点,别让跨节点访问内存。
硬件加速与 FPGA
纯软件优化到微秒级就到头了。再往下走,行情解析和订单生成可以下沉到 FPGA 网卡。FPGA 收到 UDP 包直接在网卡上解析出价格和数量,通过 PCIe 写进主机内存的固定地址。.NET 侧用 MemoryMappedFile 打开这块物理内存,轮询读取。策略算完的订单同样写回共享内存,FPGA 负责组包发出。
这个架构下 .NET 只做策略逻辑,网络和解析全部硬件化,端到端延迟能压到几百纳秒。缺点是需要专门的 FPGA 开发人员,成本高,适合自营团队。
性能测量与持续调优
没有测量就没有优化。用 Stopwatch.GetTimestamp() 打时间戳,精度到纳秒。关键路径上埋点,把从收到行情到发出订单的每个阶段耗时写到无锁环形队列,后台线程消费并聚合。
别用 DateTime.Now,它读系统时钟有几十纳秒开销。用 Environment.TickCount64 做粗粒度计时,Stopwatch 做细粒度。延迟直方图用 HdrHistogram 的 .NET 移植版,能看出 P99、P999 的尾部延迟。
每次改动只调一个变量,跑够样本量再看数据。高频交易里直觉经常是错的,稳定的低延迟比偶尔的极低延迟更重要。
