C++、Rust 和高频交易:确定性延迟决定争论
简介
编程语言的争论通常是可以容忍的,因为大多数系统都能承受得起小剧场的费用。服务有点低效,队列比应有的更宽,重试政策做了一些道德上有问题的事情,每个人都在继续前进,因为产品仍然有效,收入仍然下降,并且延迟图表以一种可生存的方式变得丑陋。团队可能会花费数周的时间争论纯洁性,因为系统本身足够礼貌,不会立即打他们耳光。
高频交易不那么情绪化。它并不关心本季度哪种语言赢得了互联网,哪种会议演讲有最干净的幻灯片,或者哪种重写举措让人们感觉未来终于到来了。它关心市场数据是否成为状态,状态成为决策,决策成为窗口关闭之前的订单。在这种环境下,无法经受衡量的优雅观点很快就会被抢劫,而且通常是在没有任何警告的情况下。
这就是为什么 HFT 中的 C++ 和 Rust 的问题很有趣。 HFT 是迫使整个论点在实际系统行为中兑现的罕见领域之一。热路径要么在压力下保持其形状,要么不保持其形状。尾部延迟要么保持规律,要么不规律。重播要么说真话,要么说假话。那里的建筑不是一个性格测试。这是一张附有时钟的发票。
HFT 对于账单的来源异常诚实。成本在匹配或执行窗口之前和之后开始。它们在提要解析、书籍维护、序列化、跨核心通信、负载下抖动以及所有微小的“可能很好”的决策中积累,一旦系统受到真实场地流量的影响,这些决策就会成为公众的耻辱。市场有一种残酷的天赋,可以将模糊的工程语言转化为确切的损失。
这也是为什么答案不是“永远C++”或“重写Rust中的所有内容,因为安全性很好,恐惧是一种商业模式。”更诚实的答案范围更窄,因此更有用。 C++ 仍然主导着最热门的 HFT 路径,因为工具、进给处理、内存控制、分析和硬件相关实践的周围世界仍然非常 C++ 形状。 Rust 围绕该核心确实有用,有时在其精心选择的部分内确实有用,但它并没有消除一个基本事实,即低延迟交易惩罚抽象错误的速度比大多数团队重命名计划的速度要快。
因此,正确的对话与身份无关。这是关于系统边界的。堆栈的哪些部分需要对内存、布局、队列、关联性和线路行为进行严格控制?哪些部分从更强的正确性约束和更安全的默认值中受益最多?哪些部分值得混合处理而不是部落纯粹?这些问题远没有语言说教那么迷人,但它们也是在与生产接触中幸存下来的问题,也是让团队围绕证据而不是口号进行合作的问题。
为什么 HFT 让糟糕的技术理念看起来代价高昂
HFT 非常擅长揭露一个常见的工程谎言:平均行为就足够的谎言。在许多普通产品中,系统可以保持受人尊敬,同时将偶尔的混乱隐藏在吞吐量、重试或用户耐心背后。在 HFT 中,平均延迟很有趣,但尾部行为通常是真正让你感到羞辱的部分。一个看起来很快但在错误的时间抽搐的系统在任何商业意义上都不是一个快速系统。这是一个附有基准的信心技巧。
这就是为什么 HFT 工程师对不精确的抽象变得过敏。他们了解到热路径上的一项额外分配并不是“只是一项分配”。它是一个可能的抖动源。一个队列跃点并不是“只是一个队列跃点”。这是另一个时间被储存、协调扩大、可见性变得更差的地方。一种对缓存不利的结构会成为对通过系统的每个市场事件的持续征税。将其乘以实际的进料量,幻灯片中的设计选择突然成为预算中令人失望的重复项目。
领域的残酷之处在于它也会惩罚部分解释。团队可能会识别出一个明显的延迟源,但仍然会错过真正的肇事者,因为这条链是累积的。内存布局决策会扩大缓存未命中范围。这扩大了排队窗口。这改变了系统在突发流量下的行为方式。这改变了“罕见”分支行为出现的顺序。然后有人走进尸检并说问题是“网络噪音”,这是“我们还没有说完真相”的工程代码。
Rust 以合法的力量进入这个对话,因为内存安全很重要,并发正确性很重要,系统代码应该有比“在坑上玩杂耍时要小心”更好的默认值。那部分是真的。但 HFT 并不会孤立地奖励真理。它奖励组合的真理。安全很重要,是的。成熟的提要处理程序、稳定的 ABI 边界、重放工具、配置文件驱动的迭代、成熟的交易所集成文化以及在市场不友善时准确检查机器正在做什么的能力也是如此。在大多数 HFT 环境中,C++ 仍然伴随着更多周围的基础设施。
这是团队和工程领导者应该抵制纯度叙述的原因之一。如果周围的生态系统、工具和团队经验不支持实际的交付路径,那么一种语言可能在狭窄的维度上表现出色,但对于堆栈中对时间最敏感的部分来说仍然是错误的默认值。 HFT 是可爱的当地真理去了解整条道路仍然更重要的地方。如果最终的系统在操作上不连贯,那么堆栈并不关心一项声明在道德上是否优雅。这也是团队生态比人们承认的更重要的地方。一个具有严格的重放工具、共享延迟词汇和令人厌烦的良好分析习惯的合作团队通常会胜过一个对证据的品味一直令人困惑的更时尚的团队。 HFT 奖励强大的技术文化比奖励时尚的迁移能量更可靠。
堆栈不是一回事,因此语言选择不应假装不是一回事
严肃的系统工作中最愚蠢的错误之一就是谈论“HFT堆栈”,就好像它是一个具有一种首选语言的单一技术有机体。它未通过该测试。它是压力和失败成本截然不同的路径的集合。
市场数据摄取路径具有一种气质。订单簿更新路径还有另一个。战略逻辑可能在数字上密集但结构上狭窄。风险检查通常对延迟敏感,但也以一种无聊的、成人的、具有法律后果的方式对正确性敏感。模拟和重放基础设施可能会重视确定性和内省,而不是原始的纳秒虚荣。控制平面工具、部署助手和操作员界面对可靠性、可维护性和集成卫生的关心远远超过了对客户永远不会看到的路径缩短五微秒的关心。
这些层之间也存在人工操作差异。有些路径每天都会被更广泛的团队修改。有些人很少接触,而且只在监督下接触。有些组件需要积极的可追溯性,因为合规性或审计最终会提出棘手的问题。有些只需要严格限制的性能和出色的重播。将这些视为一项决策导致组织最终要么过度现代化平静的组成部分,要么忽视危险的组成部分。
这很重要,因为它通常是明智的 C++ 和 Rust 对话开始的地方。当道路极其热门、注重硬件、注重集成并且已经被多年的本地操作实践所包围时,C++ 仍然是最强大的。当路径仍然很重要,但更强的违约、更清晰的所有权和更窄的内存风险暴露的经济价值超过了生态系统摩擦的成本时,Rust 变得更具吸引力。
在实践中,这通常会导致混合结果。最热门的提要处理和网关路径位于 C++。重放工具、配置验证、某些风险侧帮助程序、消息标准化实用程序、审计工具或面向内部操作员的组件可能是优秀的 Rust 候选者。这是建筑的成年期。该系统被视为一组真实的边界,而不是具有数据中心的语言狂热者。
这就是许多重写提案最终变得诚实的地方。一旦团队逐条映射堆栈路径,单一通用答案的幻想就会崩溃。这种崩溃是健康的。它允许组织优化证据、可维护性、操作信任和交付节奏,而不是为了简单口号的情感安慰。
其中 C++ 仍然拥有最热门的路径
C++ 在 HFT 中保持其地位,其原因并不像外界有时想象的那么神秘。第一个原因是内存和布局控制。 HFT 热路径关心哪些数据共存、结构在缓存中的行为方式、所有权在负载下如何显示,以及当市场不再有礼貌时系统是否可以保持分配纪律。 C++ 仍然让工程师异常直接地使用这些选择,而且它是在一个生态系统中实现的,这个生态系统已经花了几十年的时间来了解哪些“小”成本秘密很大。
第二个原因是工具密度。 HFT 中的 C++ 不仅仅意味着一种语言。它意味着编译器、消毒器、火焰图、HFT、VTune、重放工具、交换适配器、排队民间传说、分配器专业知识以及在财务压力下积累的大量性能战争故事。那里的团队不是从零开始的。他们继承了深厚的运营文化,这种文化很重要,因为 HFT 对经过衡量的迭代的奖励远远超过了言辞的简洁。
第三个原因是积分引力。交换、本机网络路径、数据包捕获工具、内核相邻优化、FPGA 相邻基础设施以及整个低延迟生态系统在 C 和 C++ 世界中仍然非常舒适。 Rust 可以与那个世界交互,有时非常有效,但“可以交互”与“是整个系统中摩擦最小的路径”不是一回事。在严重的HFT中,摩擦并不是情感上的不便。它同时可能是延迟税、调试税和交付税。
在人工智能时代还有一个更重要的微妙原因:C++ 只是在这项工作中拥有更多可用的操作内存。在低延迟系统中,人工智能编码系统、代码搜索、公共示例、供应商片段、优化器民间传说和调试跟踪在 C++ 周围比 Rust 周围更加密集。这并不会让 C++ 变得更高贵。它使人类和人工智能工具更容易在丑陋的真实代码库中进行协作,而这些代码库的魅力几年前就已经消失了。
另一个优势是许多 HFT 团队已经将 C++ 转变为机构肌肉记忆。他们知道如何在场地压力下分析它。他们知道如何从可疑路径中剥离分配。他们知道“在微基准测试中足够快”在生产中即将变得错误时听起来是什么样子。这些生活知识在操作上是宝贵的。一个已经知道如何保持 C++ 诚实的团队不应该仅仅因为一种新的语言在孤立时感觉更干净而轻易地抛弃它。
这就是为什么 C++ 作为周围的工艺系统而不是单独的语法仍然是最强大的。一旦添加了内部库、测试工具、捕获工具、线程亲和性习惯、释放能力和诊断工作流程,您就不再在真空中将一种语言与另一种语言进行比较。您正在将整个交付生态系统与另一个进行比较。在HFT中,生态系统常常超越理想。## Rust 实际上有帮助而不是表现道德
Rust 在解决实际问题时最有帮助,而不是充当架构图的个性配件。在 HFT 中,最强的 Rust 用例通常出现在热门核心周围,而不是其绝对中心。
Rust 对于正确性失败代价高昂但未用显微镜测量延迟预算的组件非常有用。消息验证层、配置和部署工具、某些协议规范化路径、控制服务、管理实用程序、离线分析器和内部操作员工具可以从该语言对明确性的偏向中受益。重点不是要看起来现代。关键是要减少愚蠢的、重复的、结构上可以避免的错误,这些错误会分散人们对更重要工作的注意力。
当团队拥有正确的专业知识并且边界诚实时,Rust 还可以帮助精心选择近热组件。如果团队能够控制 FFI 和分配情况,并且提前了解周围的生态系统负担,而不是在凌晨 2:40 推出时无人想要的情况下发现,那么低延迟解析器、有界状态机或确定性基础设施可能是可靠的 Rust 候选者。
它对于工作周围的工作也常常有帮助。捕获处理器、离线书籍重放器、审计助手、部署验证或与策略相关的基础设施受益于更严格的所有权和更清晰的界面,即使它们不是绝对的纳秒战场。这些部分很重要,因为它们决定了团队诊断事件、重现异常以及安全地从怀疑转向验证理解的速度。
但这正是团队需要纪律的地方。当Rust作为基于信仰的翻新被放入本地交易堆栈中间时,它并没有什么价值。当边界清晰、测量路径明显并且集成的运营成本低于其所创造的安全性或可维护性收益时,它是有价值的。否则,该项目将成为一个美丽的案例研究,说明如何花费大量的工程时间来消除不确定性。
真正的反模式是使用 Rust 来掩盖架构清晰度的缺失。如果团队无法解释所有权从哪里开始、测量延迟的位置、缓冲区交叉的位置以及压力下恢复的位置,那么更改语言将无法挽救设计。这只会使失败变得更加双语。
界限比讲道更重要
C++ 与 Rust 讨论中的一个常见错误是假设使用 Rust 自动消除危险。事实并非如此。它改变了危险所在。在 HFT 中,边界问题尤其重要,因为热路径很少在语言线结束。它们终止于网络边界、队列边界、调度边界、FFI边界和数据布局边界。
如果 Rust 组件必须跨入 C++ 交换适配器、与本机队列通信、将数据传递给具有严格布局假设的策略引擎,或者维持跨边界转换的确定性行为,那么真正的工程工作不是“我们使用 Rust”。真正的工作是如何仔细地定义和验证接缝。不安全行为仍然可能通过 ABI 不匹配、所有权混乱、隐藏副本、排队错误或时间意外造成。语言本身并不是您的治理模型。边界是。
这就是为什么成熟的团队谈论狭窄的热路径和狭窄的不安全表面。他们不依赖“默认内存安全”之类的口号来解决根本上的系统设计问题。优秀的团队会提出更丑陋但更有用的问题。副本发生在哪里?队列跳跃在哪里?哪一方拥有缓冲区?分配哪个路径?背压期间会发生什么?什么是可重播的?哪些内容可以单独进行基准测试,哪些内容必须端到端进行基准测试,因为本地的成功长期以来都有可能导致全球的失望?
边界清晰还可以创造组织的理智。一旦接缝明确,团队就可以在不失去责任的情况下分担责任。性能工程师知道要测量什么。平台人知道什么不能随便碰。安全和风险团队可以推断故障表面。团队和领导层获得技术解读,避免房间里的争论。良好的边界对编译器和公司都有帮助。
这就是 HFT 架构受益于合作、生态系统意识而非英雄姿态的原因之一。最好的系统通常不是由一个证明纯度的人构建的。它们是由一个在接口、测量和故障责任归属上达成一致的团队构建的,即使市场不再友好,系统仍然可以解释。
值得优先解决的实际案例
最聪明的第一个项目很少是“重写热路径”。从技术上讲,这相当于进入一所房子,并决定第一个有用的行动是更换整个骨架,然后再检查哪根管道已经淹没了厨房。
更好的第一个项目是以下之一:
饲料处理机证据工作
如果团队争论解析、标准化、排队或切换是否真的是延迟问题,请首先构建证据路径。捕获代表性流量,确定性地重放它,并迫使系统承认时间和抖动实际进入链的位置。大多数HFT系统在这里不需要更多的意识形态。他们需要更好的测谎仪。
该证据路径应该足够好,以便在需要硬优先级调用时,性能人员、开发人员和领导层可以使用相同的跟踪。一旦时机故事被分享,一半的政治摩擦就会消失,因为房间里不再与谣言进行谈判。
网关和风险边界清理许多堆栈并没有被核心策略逻辑破坏。它们因风险、网关逻辑和运营协调之间边界的草率而被毁掉。在这些接缝处仔细重写或重组可以提高可靠性和可诊断性,而无需首先接触绝对最热的循环的商业风险。
这也是更强的语言保障能够创造真正经济价值的地方。如果订单验证、限制和风险消息传递变得更容易推理,整个系统就会变得更加平静。平静的系统改变得更快,管理起来也更便宜。
混合控制平面清理
如果操作员工具、部署助手、恢复实用程序或重放工具很脆弱,那么 Rust 可能是一个强有力的候选者。这些组件通常会影响整个组织的健康状况,即使它们不位于最快的微秒路径中。更干净的工具可以使炎热的系统更加平静,而不必假装庄园中的每个二进制文件都应该使用相同的语言。
这里隐藏的胜利是健康。更好的工具可以减少团队为了回答基本操作问题而必须在凌晨 2 点进行的考古工作。这意味着更少的紧急仪式、更少脆弱的单人系统以及更好的长期技术合作。在成熟的工程组织中,这比人们大声说出来的更重要。
动手实验室:构建一个微小的序列间隙检测器并使其诚实
让我们保持实验室小而实用。 HFT系统在达到迷人的策略逻辑之前很久就通过顺序规则来生存和消亡。这个玩具程序重播类似提要的流并报告出现间隙的位置。
练习的重点不是您将部署这个确切的代码。重点是培养在压力下保持确定性状态的习惯。序列纪律是交易系统首先证明其尊重证据或证明其仍在虚张声势的地方之一。
Rust```cpp
include <cstdint>
include <iostream>
include <string>
include <vector>
struct Packet { std::uint64_t seq; std::string payload; };
struct Gap { std::uint64_t expected; std::uint64_t received; };
class GapDetector { public: void onpacket(const Packet& packet) { if (!started) { expected = packet.seq + 1; started = true; return; }
if (packet.seq != expected_) {
gaps_.push_back({expected_, packet.seq});
}
expected_ = packet.seq + 1;
}
const std::vector<Gap>& gaps() const {
return gaps_;
}
private: bool started_ = false; std::uint64t expected = 0; std::vector<Gap> gaps_; };
int main() { std::vector<Packet> replay{ {1001, "AAPL bid"}, {1002, "AAPL ask"}, {1003, "MSFT bid"}, {1007, "MSFT ask"}, {1008, "NVDA bid"}, {1011, "NVDA ask"} };
GapDetector detector;
for (const auto& packet : replay) {
detector.on_packet(packet);
}
if (detector.gaps().empty()) {
std::cout << "no gaps\n";
return 0;
}
for (const auto& gap : detector.gaps()) {
std::cout << "gap expected=" << gap.expected
<< " received=" << gap.received << "\n";
}
}
在 Linux 或 macOS 上:```bash
g++ -O2 -std=c++20 -o gap_detector main.cpp
./gap_detector
```在 Windows 上:```powershell
cl /O2 /std:c++20 main.cpp
.\main.exe
```预期输出:```text
gap expected=1004 received=1007
gap expected=1009 received=1011
```### 为什么这个小练习很重要
因为它迫使我们进行正确的思考:
* 确定性状态更新
* 诚实的排序
* 理论前重播
* 有界的、可测量的行为
这已经比数量惊人的会议幻灯片还要HFT。
如果您想让练习更加真实,请添加时间戳、迟到数据包和特定于场地的会话重置。重要的是不要让代码变得更加戏剧化。重要的是建立一种反射,即每个关于数据流的声明都应该是可测试的、可重播的,并且足够小以便能够解释。
## 爱好者测试任务
1. 将相同的检测器移植到 Rust 并比较边界清晰度、依赖性摩擦以及与现有工具的配合。
2. 扩展重播,以便丢失的数据包稍后可以无序到达,然后决定检测器是否应该缓冲、拒绝或标记它们。
3. 添加计时并测量矢量支持的重放和环形缓冲区支持的重放之间的差异。
4. 在热路径上引入一个不必要的分配,并测量“小”决策开始污染结果的速度。
5. 在 HFT 内添加一个日志分支,并观察当不小心放置时可观察性被破坏的速度有多快。
## 总结
HFT 中真正的 C++ 和 Rust 对话并不是关于哪种语言值得更好的神话。它涉及系统的哪些部分需要直接控制,哪些部分受益于更强的默认值,以及哪些边界可以足够诚实以支持混合设计而不会产生错觉。
C++ 仍然主导着最热门的 HFT 路径,因为该领域奖励对内存布局、队列、线路行为、分析、重放以及与成熟的低延迟生态系统集成的控制。当正确性、明确性和可维护性比额外的生态系统摩擦成本创造更多价值时,Rust 非常有用。两者都可以属于一个严肃的堆栈。成年人的做法是决定在哪里,并让证据而不是语言狂热者来评分。
做到这一点的团队会做一些非常平淡却非常有效的事情。他们不再问哪种语言可以拯救他们,而是开始问哪个边界值得什么样的纪律。这种转变听起来很温和,但这是作为品牌的架构和作为运营真理的架构之间的区别。在HFT中,真相通常会更长久。
## 参考文献
1. 纳斯达克 TotalView-ITCH 规范:Rust
2. FIX 交易社区标准:C++
3.DPDK文档:ITCH
4. Linux 时间戳文档:FIX
5. Brendan Gregg 谈火焰图:Linux
6. Rust 性能手册:[https://nnethercote.github.io/perf-book/](https://nnethercote.github.io/perf-book/)