nautilus_trader Lighter 适配器基准测试:Schnorr 签名、Poseidon2 哈希与 Go 官方栈的性能对比全指南
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
本篇技术指南围绕 crates/adapters/lighter/benches/BENCHMARKS.md 展开,系统讲解 Nautilus 交易引擎中 Lighter L2 适配器的基准测试方法论、可复现的测量流程、已发布的签名性能基线,以及 Rust 实现与官方 Go 栈(lighter-go+poseidon_crypto)的逐项对比结论。读完本文,你将掌握如何在本机复现同一组数字、如何正确解读并引用这些基准,以及 Lighter 签名热路径(Schnorr 签名 / 验签 / 公钥派生 / 交易哈希)在 Rust 侧的底层成本构成。
一、基准测试的目标与定位
Lighter 是一个基于 Rust 的高性能 L2 交易协议,nautilus_trader 通过nautilus-lightercrate 与其对接(见 crates/adapters/lighter/Cargo.toml 中的nautilus-lighter包声明)。L2 链上交易的提交路径包含 Poseidon2 哈希与 Schnorr 签名等密码学原语,这是策略下单到 L2 排序器之间的关键路径,其性能直接决定高频策略的极限下单频率。
仓库根目录的 BENCHMARKING.md 明确了项目基准测试的整体政策:基准即文档,它记录了什么被认为是热路径、什么样的输入被认为是真实的、以及某一时刻的成本形态;性能声明必须附上可复现的证据。Lighter 适配器的benches/BENCHMARKS.md正是这一政策在该适配器上的落地——它是本适配器唯一被授权对外引用的签名数字来源:
- 发布签名相关性能数字时,应引用
signing_sign_verify.rs的结果; - 排查数据/执行管道回归时,使用
data.rs、exec.rs与micros.rs的组合定位瓶颈所在的层级; - 指令数级回归门槛由
signing_field_iai.rs提供。
二、测量环境:谁测的、在什么条件下测的
数字只有在可复现的条件下才有意义。BENCHMARKS.md记录的本轮测量环境如下:
| 项目 | 取值 |
|---|---|
| 测量日期 | 2026-08-19 |
| CPU | AMD Ryzen Threadripper 9980X |
| 工具链 | rustc 1.97.1,bench-ltoprofile |
| Rust profile | release 优化 +lto = "fat"+codegen-units = 1+debug = full |
| CPU 调频策略 | 固定为performance |
| ASLR | 通过setarch -R关闭 |
| Rust 数据口径 | 两次测量会话的中位 Criterion 中点(先跑一次预热) |
| Go 对照 | Go 1.26.6,-cpu=1,同样的调频与 ASLR 控制,取两次会话的ns/op中位数 |
该bench-ltoprofile 定义在仓库根 Cargo.toml:inherits = "release"、debug = "full"、strip = false、lto = "fat"、incremental = false、codegen-units = 1。它的设计意图是让基准数字与生产 release profile 的代码生成质量一致(lto = "fat"、codegen-units = 1),同时保留调试符号供剖析器使用。Cargo.toml 中对应注释(Cargo.toml)明确要求:任何对外发布/引用的基准数字(PR 描述、release note、各适配器BENCHMARKS.md)都必须使用该 profile,并配合setarch -R关闭 ASLR、将 CPU 调频器设为performance以获得最紧致、最可复现的数据。
重要前提:绝对数值因机器而异,只有同机对比的差值才有意义。文档明确说明测量期间该主机还有其他交互会话,噪声地板偏高,因此应当比较的是比率(ratio)而不是绝对微秒数。
三、如何复现:完整操作步骤
复现流程的核心思想是:先一次性施加主机控制项,然后在控制项保持生效期间依次运行 Rust 与 Go 两侧,最后恢复调频策略。
3.1 主机控制项
sudo cpupower frequency-set -g performancesetarch "$(uname -m)" -R用于在计时进程两侧同时关闭 ASLR。taskset是可选的额外隔离手段,本轮数字未使用。
3.2 Rust 签名基线
CARGO_BUILD_JOBS=16 setarch "$(uname -m)" -R cargo bench --locked \ --profile bench-lto -p nautilus-lighter --bench signing_sign_verify该命令需完整运行三次:第一次结果作为预热丢弃,报告后两次测量会话的中位数。这里的--bench signing_sign_verify对应 crates/adapters/lighter/Cargo.toml 中注册的[[bench]]目标,harness = false表示由 Criterion 自带测试框架接管。同一文件中还注册了signing_poseidon2、signing_field、signing_curve、signing_field_iai、data、exec、micros共八个 bench 目标。
3.3 Go 对照(官方栈对比)
crate 不随仓库分发 Go 源码,需要在仓库外重建两个 scratch 模块,粘贴文档给出的清单后运行。需要 Go 1.23+ 工具链;若go version缺失,从官方渠道安装。
- 原始原语套件将
elliottech/poseidon_crypto固定到fbd3713966eeeb9496166db9b599d4a3bb7b9e2b——与 crate 的 fixture 向量同一修订版本。输入与common/mod.rs中的字节完全一致。 - SDK 套件将
elliottech/lighter-go固定到cef81af980850607a66213fcca5f3e76ddebda7e(即v1.0.9-0.20260812092842-cef81af98085),该模块依赖poseidon_cryptov0.0.15。其中ConstructCreateOrderTx对与 Rustcreate_order_tx()相同的 create-order 字段执行校验、哈希与签名。官方 Go 客户端通过poseidon_crypto在进程内签名,不会加载lighter-python使用的闭源共享库——这一点保证了对比的是纯公开算法实现。
保持 governor 为performance,创建 scratch 目录树,粘贴各文件、go mod tidy,每套件各跑两次,取ns/op中位数:
mkdir -p /tmp/lighter-signing-go/primitives /tmp/lighter-signing-go/sdk # paste go.mod and bench_test.go into each directory cd /tmp/lighter-signing-go/primitives go mod tidy setarch "$(uname -m)" -R go test -bench=. -benchtime=3s -cpu=1 cd /tmp/lighter-signing-go/sdk go mod tidy setarch "$(uname -m)" -R go test -bench=. -benchtime=3s -cpu=1 sudo cpupower frequency-set -g powersave原始原语套件清单
/tmp/lighter-signing-go/primitives/go.mod:
module lighter-signing-primitives go 1.23 require github.com/elliottech/poseidon_crypto v0.0.0-20260410093228-fbd3713966ee/tmp/lighter-signing-go/primitives/bench_test.go:
package primitives import ( "testing" curve "github.com/elliottech/poseidon_crypto/curve/ecgfp5" g "github.com/elliottech/poseidon_crypto/field/goldilocks" gFp5 "github.com/elliottech/poseidon_crypto/field/goldilocks_quintic_extension" schnorr "github.com/elliottech/poseidon_crypto/signature/schnorr" ) func fixedSk() curve.ECgFp5Scalar { bytes := []byte{ 0x0b, 0x8e, 0x0f, 0x63, 0xc2, 0x4d, 0x8b, 0xaa, 0xcd, 0x9d, 0x29, 0xad, 0x4e, 0x9a, 0x4b, 0x73, 0xc4, 0xa8, 0xd2, 0xbb, 0x8b, 0x16, 0xdc, 0x4f, 0xa9, 0xd7, 0xc2, 0xe1, 0xd3, 0xa8, 0xb1, 0xf0, 0xe8, 0xd3, 0xa4, 0xc5, 0xb6, 0xe7, 0xf0, 0x01, } return curve.ScalarElementFromLittleEndianBytes(bytes) } func fixedK() curve.ECgFp5Scalar { var bytes [40]byte bytes[0] = 0x42 bytes[7] = 0x01 bytes[16] = 0x91 bytes[24] = 0x37 return curve.ScalarElementFromLittleEndianBytes(bytes[:]) } func fixedHashedMsg() gFp5.Element { return gFp5.Element{ g.GoldilocksField(0x0123_4567_89AB_CDEF), g.GoldilocksField(0xFEDC_BA98_7654_3210), g.GoldilocksField(0x1111_2222_3333_4444), g.GoldilocksField(0x5555_6666_7777_8888), g.GoldilocksField(0x0000_0001_0000_0001), } } func fixedPk() gFp5.Element { return schnorr.SchnorrPkFromSk(fixedSk()) } func fixedSignature() schnorr.Signature { return schnorr.SchnorrSignHashedMessage2(fixedHashedMsg(), fixedSk(), fixedK()) } var ( sinkSig schnorr.Signature sinkBool bool sinkPk gFp5.Element ) func BenchmarkSchnorrSign(b *testing.B) { sk := fixedSk() k := fixedK() msg := fixedHashedMsg() b.ResetTimer() for i := 0; i < b.N; i++ { sinkSig = schnorr.SchnorrSignHashedMessage2(msg, sk, k) } } func BenchmarkSchnorrVerify(b *testing.B) { pk := fixedPk() msg := fixedHashedMsg() sig := fixedSignature() b.ResetTimer() for i := 0; i < b.N; i++ { sinkBool = schnorr.IsSchnorrSignatureValid(pk, msg, sig) } } func BenchmarkSchnorrPkFromSk(b *testing.B) { sk := fixedSk() b.ResetTimer() for i := 0; i < b.N; i++ { sinkPk = schnorr.SchnorrPkFromSk(sk) } }官方 SDK 套件清单
/tmp/lighter-signing-go/sdk/go.mod:
module lighter-signing-sdk go 1.23.0 require github.com/elliottech/lighter-go v1.0.9-0.20260812092842-cef81af98085/tmp/lighter-signing-go/sdk/bench_test.go:
package sdk import ( "testing" "github.com/elliottech/lighter-go/signer" "github.com/elliottech/lighter-go/types" "github.com/elliottech/lighter-go/types/txtypes" ) const ( chainID uint32 = 304 accountIndex int64 = 12345 apiKeyIndex uint8 = 5 nonce int64 = 42 expiredAt int64 = 1_777_809_907_000 ) func fixedSkBytes() []byte { return []byte{ 0x0b, 0x8e, 0x0f, 0x63, 0xc2, 0x4d, 0x8b, 0xaa, 0xcd, 0x9d, 0x29, 0xad, 0x4e, 0x9a, 0x4b, 0x73, 0xc4, 0xa8, 0xd2, 0xbb, 0x8b, 0x16, 0xdc, 0x4f, 0xa9, 0xd7, 0xc2, 0xe1, 0xd3, 0xa8, 0xb1, 0xf0, 0xe8, 0xd3, 0xa4, 0xc5, 0xb6, 0xe7, 0xf0, 0x01, } } func createOrderReq() *types.CreateOrderTxReq { return &types.CreateOrderTxReq{ MarketIndex: 1, ClientOrderIndex: 7, BaseAmount: 1_000_000, Price: 25_000_000, IsAsk: 0, Type: txtypes.LimitOrder, TimeInForce: txtypes.ImmediateOrCancel, ReduceOnly: 0, TriggerPrice: 0, OrderExpiry: txtypes.NilOrderExpiry, } } func transactOpts() *types.TransactOpts { account := accountIndex apiKey := apiKeyIndex n := nonce return &types.TransactOpts{ FromAccountIndex: &account, ApiKeyIndex: &apiKey, ExpiredAt: expiredAt, Nonce: &n, } } var ( sinkTx *txtypes.L2CreateOrderTxInfo sinkHash []byte ) func BenchmarkConstructCreateOrder(b *testing.B) { key, err := signer.NewKeyManager(fixedSkBytes()) if err != nil { b.Fatal(err) } tx := createOrderReq() ops := transactOpts() if _, err := types.ConstructCreateOrderTx(key, chainID, tx, ops); err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { sinkTx, err = types.ConstructCreateOrderTx(key, chainID, tx, ops) if err != nil { b.Fatal(err) } } } func BenchmarkCreateOrderHash(b *testing.B) { txInfo := types.ConvertCreateOrderTx(createOrderReq(), transactOpts()) if err := txInfo.Validate(); err != nil { b.Fatal(err) } var err error b.ResetTimer() for i := 0; i < b.N; i++ { sinkHash, err = txInfo.Hash(chainID) if err != nil { b.Fatal(err) } } }两个套件中的固定私钥、固定 nonce、固定哈希消息等 fixture 与 Rust 侧的common/mod.rs完全同源:例如fixed_sk()、fixed_k()、fixed_hashed_msg()在 Go 与 Rust 中字节级一致,chainID = 304对应 Rust 侧CHAIN_ID常量,CreateOrderTxReq的字段取值(MarketIndex: 1、ClientOrderIndex: 7、BaseAmount: 1_000_000、Price: 25_000_000等)也与 Rustcreate_order_tx()fixture 一一对应。这是两侧结果可比的前提。
文档还提示:噪声抑制的通用方法论与政策详见仓库根的 BENCHMARKING.md。
四、已发布签名基线(signing_sign_verify.rs)
signing_sign_verify.rs是 Lighter 适配器对外发布的 L2 签名基线。它测量的正是用户可见的关键路径:PrivateKey::sign、PublicKey::verify、两种交易热类型的compute_tx_hash、sign_tx(哈希 + 签名)、公钥派生以及build_auth_token_at(见 signing_sign_verify.rs)。
关键设计:PrivateKey::sign与sign_tx都使用固定 noncek,因此计时区域是哈希与曲线运算,而不是随机数生成——排除了 RNG 对测量结果的干扰。fixed_k()在 common/mod.rs 中构造:40 字节标量中仅有 4 个字节非零(0x42、0x01、0x91、0x37),与 Go 侧fixedK()完全一致。
| Bench | 中位数 | 吞吐量 |
|---|---|---|
signing/PrivateKey::sign | 67.1 µs | 14.9 k/s |
signing/PublicKey::verify | 139 µs | 7.19 k/s |
signing/PrivateKey::public_key | 64.6 µs | 15.5 k/s |
signing/compute_tx_hash (CreateOrder) | 1.99 µs | 503 k/s |
signing/compute_tx_hash (CancelOrder) | 994 ns | 1.01 M/s |
signing/sign_tx (CreateOrder) | 67.8 µs | 14.7 k/s |
signing/sign_tx (CancelOrder) | 66.8 µs | 15.0 k/s |
signing/build_auth_token_at | 67.8 µs | 14.7 k/s |
4.1 从源码看这些数字的构成
PrivateKey::sign/PublicKey::verify/public_key定义在 schnorr/key.rs。验证是最昂贵的原语,因为它执行双基标量乘法加上一次 Poseidon2 哈希;签名与公钥派生各自只归约为一次常数时间标量乘法。sign_tx定义在 signing/tx/encode.rs,其成本 = 签名(约 65 µs)+ 一次 create-order 哈希(约 2 µs),与上表 67.8 µs 吻合。compute_tx_hash的底层语义在 signing/tx/mod.rs 中有权威说明:每个 L2 交易体按固定顺序规约为一串 Goldilocks 域元素,用 Poseidon2 哈希成单个Fp5摘要,可选地与每笔交易的L2TxAttributes哈希聚合,最终产出 40 字节规范小端字节。排序器会重新计算同一哈希并在不匹配时拒绝交易——这解释了为什么哈希路径是交易提交的关键路径。build_auth_token_at定义在 signing/auth_token.rs,用于 L2 会话鉴权令牌构建,同样走一次签名。
五、与官方 Go 栈的对比结果
| 工作负载 | Go | Rust(bench-lto) | 加速比 |
|---|---|---|---|
Schnorr 签名(固定k) | 247 µs | 67.1 µs | 3.7x |
| Schnorr 验签 | 438 µs | 139 µs | 3.2x |
| 公钥派生 | 241 µs | 64.6 µs | 3.7x |
| CreateOrder 哈希 | 4.37 µs | 1.99 µs | 2.2x |
| CreateOrder 哈希 + 签名 | 283 µs(lighter-go) | 67.8 µs | 4.2x |
5.1 如何正确解读这张表
前三行是同一组poseidon_crypto原语,与PrivateKey::sign、PublicKey::verify、PrivateKey::public_key对应,两侧均使用固定 nonce,因此回答的是曲线与哈希实现本身的成本差。
"CreateOrder 哈希" 行是L2CreateOrderTxInfo.Hash对比compute_tx_hash。两侧都使用空属性(empty attributes),因此只运行 body 的 Poseidon2 原像哈希,是纯净的哈希成本对比。
"CreateOrder 哈希 + 签名" 行在两侧并不是同一个函数,必须注意区分:
- Go 运行
types.ConstructCreateOrderTx:校验 → 哈希 → 采样新 nonce → 签名; - Rust 运行
sign_tx:fixture nonce 已经就位,只做哈希 + 签名。
因此 Go 侧额外包含了真实官方 SDK 的成本(校验与 nonce 采样)。当问题聚焦"曲线与哈希实现的成本"时,应使用固定k的原语行;而当问题聚焦"完整官方 SDK 工作流对比"时,才使用最后一行。
六、其他基准套件:管道与指令数门槛
BENCHMARKS.md明确说明本轮未重新测量以下套件,但它们的职责边界值得了解:
| 套件 | 覆盖范围 | 用法 |
|---|---|---|
| data.rs | 入站管道:原始 WS 帧字节 → Nautilus 域类型 | 每个消息种类端到端(decode + parse + 缓存查找 + 类型构造),无 I/O、无异步运行时、无通道 |
| exec.rs | 有符号线上报文装配:类型化订单意图 → 可 POST 的签名线字节 | 组装CreateOrderTxInfo/CancelOrderTxInfo/ModifyOrderTxInfo,跑sign_tx,再经TxInfoJson渲染成线上 JSON |
| micros.rs | 组件级微基准,将管道数字拆解为构成成本 | decode_only、parse_only、原子级 Decimal/UUID/事件构造、签名组件成本 |
signing_field.rs | 单操作级墙钟噪声 | 用于原语级观察 |
signing_field_iai.rs | 原语指令数门槛(iai) | 小且确定操作的指令数信号 |
引用纪律:发布签名数字时引用signing_sign_verify.rs;micros.rs会在解码与 JSON 渲染旁边重复少量相同调用,以便将管道回归定位到具体层级,但不得把这些重复项当作第二个基线。
6.1 三个套件的源码级分工
- 入站管道(data.rs):bench group 名为
inbound_pipeline,覆盖 trades、book_deltas、book_depth10、quotes、bars、mark_price、index_price、funding_rate、fill_report、order_status 十个消息种类。所有 fixture 是内联的&'static str常量,形状与线上 Wire 格式一致(见 common/mod.rs 的fixtures模块),且刻意保持单消息种类,使被测成本可归因于一种消息。文档注释还说明这些内联字符串与test_data/ws_*.json现场抓包同形,而抓包 JSON 保留给解析器正确性测试使用。 - 执行管道(exec.rs):bench group 名为
exec_pipeline,覆盖 submit_market、submit_limit、submit_stop_market、cancel、modify 五个场景,测的是"策略命令 → L2 排序器"的完整关键路径。它构建了三类订单 fixture(限价、市价、止损市价,区分order_type0/1/2 与不同time_in_force),并引入了L2TxAttributes(integrator 账户、taker/maker 费率、skip_nonce)以贴近真实 integrator 场景。 - 组件微基准(micros.rs):通过
decode_only与parse_only把 JSON 解码与域解析拆开;通过atom/*系列测量Decimal::from_str、Price::from_decimal_dp、Quantity::from_decimal_dp、TradeId::new、UUID4::new等原子成本;签名侧则拆出compute_tx_hash、hash_to_quintic_extension、hash_two_to_quintic、mulgen_ct(生成元上的常数时间标量乘法,是 Schnorr 签名的主导成本)、sign_tx、render_create_order_json。其中wire_wrap_value与wire_wrap_raw一对对比了旧路径(serde_json::from_str成Value构建 AST)与新路径(Box<RawValue>仅校验字节不建 AST)的节省,可以直接佐证一次具体的重构收益。
七、Poseidon2 专项基准(signing_poseidon2.rs)
signing_poseidon2.rs对 Poseidon2 置换与海绵结构做专项测量:hash_no_pad在输入长度[1, RATE, 2*RATE, 3*RATE]上扫描,使吸收循环或置换本身的回归呈现为不同的曲线;permute则单独计时(见 signing_poseidon2.rs)。该套件与signing_field_iai.rs的指令数门槛共同构成签名原语层的回归防线。
八、结语:一套可复现、可引用、可追溯的基准体系
Lighter 适配器的基准体系遵循 nautilus_trader 全仓库的 BENCHMARKING.md 政策,做到了三层分工:signing_sign_verify.rs是面向外部的签名基线;data.rs/exec.rs覆盖入站与出站两条管道;micros.rs与signing_field_iai.rs提供层级化的定位手段。而 Rust 侧约 3.2x–4.2x 于官方 Go 栈的加速比,是在固定 nonce、同源 fixture、同机同控制条件下得到的可复现结论——引用任何一行数字前,请先确认复现环境与口径(bench-ltoprofile、performance调频、setarch -R关闭 ASLR),并遵守"只引用signing_sign_verify.rs、不把micros.rs重复项当基线"的纪律。
【免费下载链接】nautilus_traderProduction-grade Rust-native trading engine with deterministic event-driven architecture项目地址: https://gitcode.com/GitHub_Trending/na/nautilus_trader
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考