Latency audit
Skill paidaxing1234/latency-hunter-toolkit/skills/latency-audit
5 quant-engineering skills for Claude Code: backtest audit, C++ ▎ low-latency hot-path review, Binance/OKX connector scaffolding, ▎ market-data quality audit & risk-config audit. No alpha, no keys.
npx -y skills add paidaxing1234/latency-hunter-toolkit --skill latency-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
C++ 低延迟/高频交易引擎的「热路径猎手」。逐项审查会拖慢热路径、放大尾延迟(P99/P99.9)的工程坑——锁争用、false sharing、cache miss、热路径分配、syscall、序列化、分支预测失败、NUMA 跨节点、TLB miss、PyBind11 GIL——并定位到 文件:行,按严重度(致命/高危/中/低)输出一张「热路径体检报告」。 触发词(中):热路径审查、低延迟审查、延迟猎手、锁争用、伪共享、缓存未命中、尾延迟、热路径分配、NUMA 亲和、TLB miss、GIL 争用、热路径体检。 触发词(英):latency audit、hotpath、hot path、low-latency、false sharing、cache miss、lock contention、tail latency、NUMA、TLB miss、huge page、pybind11 GIL、latency-audit。 适用:用户把 tick 回调/撮合/下单/序列化/无锁队列/PyBind11 混合策略等 C++ 热路径代码丢过来,问「这段会不会拖慢热路径/为什么尾延迟突刺/帮我查锁和分配/有没有 false sharing/NUMA 绑对了吗」。 不适用:纯功能 bug(逻辑错、崩溃)、非性能问题、非 C++ 代码、要求承诺具体延迟微秒数字或替代真实 benchmark/profiling。
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
18.9 KB, ~7.1k tokens by cl100k_base, as published. Nobody here has run it
latency-audit / 热路径猎手
核心理念:尾延迟是钱
在高频交易里,P99.9 尾延迟不是统计噱头,是真金白银。热路径上每一个锁、每一次堆分配、每一个 syscall、每一份多余拷贝,实盘都用滑点、排队靠后、错失成交还回来。均值(mean)漂亮没用——99% 的 tick 跑得飞快、偶尔一次 brk/futex/磁盘抖动/跨 NUMA 访问/TLB miss 就把那一笔订单的延迟从微秒打到毫秒,而那一笔往往就是行情剧烈、最该抢到的成交。
本 skill 是一把热路径照妖镜:不替你写引擎,而是逐项揪出「会拖慢热路径、增大尾延迟」的工程坑,定位到 文件:行,按严重度排出修复优先级。
铁律:本工具只做静态工程审查,不替代实测。 所有结论都应回到 perf / clang-tidy / perf c2c / numastat / 火焰图 上验证——代码看着像坑不等于它在你的负载下真是瓶颈,优化要由 profiling 数据驱动,本报告不承诺任何具体微秒数字。
何时启用
- 用户丢来 tick 回调 / 行情解析 / 撮合 / 信号 / 下单 / 序列化 / 无锁队列 / PyBind11 混合策略 等 C++ 代码,问「这段热路径有没有坑 / 为什么尾延迟突刺 / 帮我查锁和分配 / NUMA 绑对了吗」。
- 触发词:热路径审查、低延迟审查、锁争用、false sharing、cache miss、尾延迟突刺、热路径分配、NUMA 亲和、TLB miss、GIL 争用、
latency audit、hotpath、low-latency。 - code review 阶段想专门过一遍「性能/延迟」维度(功能正确性另用通用 code review)。
不启用:纯功能 bug、逻辑错误、崩溃排查;非 C++ 代码;要你承诺「这能跑到 X 微秒」或拿它替代真实 benchmark。
审查协议
- 先圈热路径。明确哪些函数在关键路径上、每 tick/每消息都跑:
on_tick/on_md/on_message/handle_quote/ 撮合match/ 信号on_signal/ 下单send_order/ 序列化encode/decode/ 无锁队列push/pop/ PyBind11 导出的热回调。只在热路径上较真——冷路径(配置加载、日志落盘、对账、启动初始化)用 mutex、分配、格式化都允许,别误报。 - 逐类过三大陷阱。对圈出的热路径,依次走「并发/锁/伪共享/NUMA/GIL」「内存/缓存/分配/TLB」「syscall/IO/序列化/网络/分支」三类清单(详见
references/latency-pitfalls.md)。 - 定位到 文件:行,给可验证的检测手段。每条命中都要落到具体
文件:行,并给出「怎么用工具坐实」——grep模式、clang-tidycheck、perf/perf c2c/perf lock/numastat/perf stat -e dTLB-load-misses/火焰图 的观测点。基于真实代码定位,不臆测;吃不准是不是热路径就标注「需确认是否在热路径」。 - 按严重度汇总,输出「热路径体检报告」。
- 收尾强调实测。报告末尾固定提示:用
perf实测 P99/P99.9、用perf c2c验 false sharing、用numastat/numactl --hardware验 NUMA 落点、用perf stat看 TLB miss、用火焰图定位真实热点,代码审查只负责找嫌疑、不下延迟判决。
三大类陷阱
完整清单(每条含 why / detectHint / fix)见 references/latency-pitfalls.md,下面是每类最致命的速查。
① 并发 / 锁 / 伪共享 / NUMA / GIL(concurrency)
- 【致命】热路径上
std::mutex锁争用 — 争用即陷内核(futex),上下文切换 1-5μs,被抢占可等整个调度周期(ms 级),P99.9 直接炸。热路径单生产单消费改 SPSC ring,跨线程状态用 atomic acquire/release。 - 【致命】持锁执行 I/O / 耗时操作 — 临界区内
send/redisCommand/new/格式化,把锁持有时间从 ns 拉到 ms,拖死所有等锁线程。锁内只做「换指针/拷快照」,I/O 甩给旁路线程。 - 【致命】NUMA 跨节点访问 / 线程-内存-网卡中断未同节点亲和 — 多路服务器上,交易线程跑在 node0 却访问分配在 node1 的对象池/订单簿,远端内存读延迟近乎翻倍(数十 ns 量级上探),
first-touch默认策略常把内存分配到错误 node;网卡 DMA/中断落在非交易 node 还要跨 QPI/UPI 搬运。隐蔽且只在多 socket 机器上爆。把交易线程、其内存、网卡中断全部绑到同一 NUMA node:numactl --cpunodebind=0 --membind=0,启动期在目标线程上 first-touch 预触摸所有热内存,网卡中断亲和(/proc/irq/*/smp_affinity)对齐到本 node。验证:numastat -p <pid>看numa_miss/other_node,perf c2c看 remote HITM。 - 【致命】PyBind11 / 嵌入式 Python 在热路径持有或争用 GIL — C++/Python 混合引擎里,若 tick 回调里回调进 Python、或 C++ 热段未
gil_scoped_release就跑长逻辑,等于全程握着一把进程级全局锁,所有线程被 Python 解释器串行化,彻底毁掉多核与确定性。热路径纯 C++ 段必须py::gil_scoped_release;绝不在 tick/撮合回调里同步调 Python;跨语言只传 POD/句柄,Python 只在冷路径(参数下发、收盘统计)介入。验证:grep -n gil_scoped_acquire|PyEval_|Py_BEGIN,火焰图看热路径下挂take_gil/PyEval_EvalFrame。 - 【高危】
std::queue/std::deque+ mutex 当跨线程队列 — deque 每次 push 可能堆分配,叠加锁=双重杀手。SPSC 通道换定长 2 的幂数组 ring buffer。 - 【高危】false sharing:多线程高频写挤同一 cache line — ring 的 head/tail、各线程计数器若同 line,MESI ping-pong 几十~上百 ns。字段加
alignas(64)隔到独立 cache line,perf c2c看 HITM 坐实。 - 【高危】热路径
condition_variable阻塞唤醒 — wait/notify 走 futex,唤醒延迟微秒级且受调度影响。超低延迟通道改 busy-poll,或 spin-then-block 混合。 - 【高危】跨线程传
shared_ptr引用计数原子争用 — 控制块原子 inc/dec = 隐藏 false sharing,热路径按值传更触发拷贝抖动。改const&/裸指针/对象池索引。 - 【高危】关键线程不绑核 / 频繁创建销毁
std::thread— 不绑核被调度器核间迁移,迁移即丢 L1/L2/TLB,造缓存尖刺。关键线程启动期常驻 +pthread_setaffinity_np绑独占核,配合isolcpus/nohz_full/rcu_nocbs,必要时SCHED_FIFO。 - 【中】自旋忙等退避策略不当 — 裸空转饿死 SMT 兄弟核 + 烧功耗,过度
yield引入调度抖动。分级退避:短自旋带_mm_pause()→ 更多自旋 → 必要时 yield;独占核可纯 busy-poll(注意是延迟换 CPU 功耗的取舍)。 - 【中】
std::atomic默认 seq_cst — x86 上 seq_cst store 插 mfence(几十 ns),多数场景只需 acquire/release,纯计数用 relaxed。显式写 memory_order。
② 内存 / 缓存 / 分配 / TLB(memory)
- 【致命】热路径动态分配
new/malloc— 走 ptmalloc arena 锁、free-list,甚至触发 mmap/brk + 缺页,数十~数百 ns 且不可预测,P99.9 突刺头号杀手。零分配热路径:启动期对象池预分配,运行期 RSS 不增长。 - 【高危】
std::string/std::vector热路径反复扩容拷贝 — 容量不足即 realloc + memcpy,SSO 超 ~15/22 字节落堆。事前reserve(maxSize),定长用std::array,复用 buffer(clear()不释放容量)。 - 【高危】热路径虚函数 / vtable 指针追逐 — vptr 加载可能 cache miss,间接跳转破坏分支预测、阻止内联。改 CRTP 静态多态 /
std::variant+visit,虚函数留冷路径。 - 【高危】
std::map/std::unordered_map热路径查找 — 红黑树/链式哈希指针追逐,节点散落堆上,几乎每跳一次 node 一次 cache miss。换 flat hash(absl::flat_hash_map/robin_hood),整型 key 直接数组索引。 - 【高危】按值传大对象 / 不必要拷贝 — 按值传
vector/大 struct = 深拷贝 + 分配。只读用const&/string_view/span,转移用move,range-for 用const auto&。小 POD(<=16B)按值反而更好,别一刀切。 - 【高危】指针追逐数据结构(链表/树) —
std::list/侵入链表每跳一节点一次依赖性 load,CPU 无法预取,N 节点 = N 次串行 miss。连续存储优先,必要时__builtin_prefetch。 - 【高危】AoS 布局 / 冷热数据混排 — 热循环只读少数字段却撑满 cache line。批量遍历改 SoA,冷热字段分离,
pahole查布局。随机访问单条记录时 AoS 更好,按访问模式选。 - 【高危】大热表 TLB miss / 未用 huge page — 几十 MB
GB 级的对象池、订单簿、行情索引用默认 4K 页时,工作集页数远超 TLB 表项(L1 dTLB 仅数十上百项),每次跨页访问触发 TLB miss + page walk(数十 ns),大表随机访问尤甚。热大表用显式 huge page(2M/1G:mmap(MAP_HUGETLB)或 hugetlbfs;别依赖透明大页 THP,其 khugepaged 合并会带来抖动,延迟敏感场景常显式关 THP 改显式 hugetlb),配mlockall(MCL_CURRENT|MCL_FUTURE)锁页防换出 + 启动期预触摸。验证:perf stat -e dTLB-load-misses,dtlb_load_misses.walk_active看 page walk 占比。 - 【中】未对齐 / 跨 cache line 的热结构 — 起始地址未按 64B 对齐或大小跨 line,一次访问横跨两条 line。热 struct 加
alignas(64),ring slot padding 到 line 整数倍,pahole查空洞。 - 【中】
noexcept缺失 / 热路径异常控制流 — throw 栈展开微秒级毁尾延迟;缺 noexcept 阻止 vector move 优化。热路径用返回码/std::expected,关键函数标 noexcept。
③ syscall / IO / 序列化 / 网络 / 分支(io)
- 【致命】热路径同步日志落盘 — 每条日志一次
write/fsync,磁盘抖动打到 ms 级,P99 突刺最常见元凶。改无锁异步日志(spdlog async / NanoLog),热路径只把二进制参数压环,后台线程格式化。 - 【致命】未设
TCP_NODELAY/ Nagle 生效 — 小包被缓冲攒 MSS 或等 ACK,叠加 delayed ACK 最高 ~40ms 停顿,极隐蔽。socket 建好立即setsockopt(TCP_NODELAY),ZMQ 设ZMQ_TCP_NODELAY。 - 【致命】同步阻塞 I/O 在热路径 — 阻塞
recv/同步redisCommand/同步 DB,交易线程陷内核睡眠,恢复 cache 全冷。全异步非阻塞:O_NONBLOCK+ epoll(ET)/io_uring,Redis 异步 API 或移出热路径。 - 【高危】JSON 文本序列化/反序列化在热路径 — DOM 库大量小对象分配 +
strtod,nlohmann::json尤慢。优先二进制协议(SBE/FlatBuffers/Cap'n Proto),必须 JSON 用simdjson+fast_float。 - 【高危】热路径字符串格式化拼接 —
to_string/sprintf/fmt::format触发分配 + locale,数百 ns。订单/symbol 用整型 ID,FIX 用预分配 buffer + 手写 itoa/ftoa。 - 【高危】高频时间戳 syscall — 每 tick
clock_gettime/system_clock::now(),clocksource 退化成非 tsc 时变真 syscall。热路径用 RDTSC/RDTSCP(启动校准),确认clocksource=tsc且 CPU 有constant_tsc/nonstop_tsc。 - 【高危】WebSocket/ZMQ 每条消息多次内存拷贝 — recv→string→message→queue 4-5 次 memcpy,吃满内存带宽污染 cache。全链路 zero-copy:
string_view/span建视图,ZMQzmq_msg_init_data,recvmmsg批量。 - 【高危】缺批量处理 / 每消息一次 syscall — syscall 次数 = 消息数,每次 100-300ns 切换。
recvmmsg/sendmmsg批量,epoll 一次处理多个 fd,应用层攒批。 - 【中】分支预测失败 / 缺 likely/unlikely — 罕见路径(风控拒单、异常)平铺成普通 if,误预测清流水线 ~15-20 cycle。冷路径标
[[unlikely]]+抽成noinline,优先上 PGO/AutoFDO。 - 【中】关键小函数未内联 / 跨 TU 调用 — 小函数定义在
.cpp无 LTO 不内联,付调用开销 + 阻断跨调用优化。热路径小函数 header inline,开-flto。别盲目always_inline大函数(撑爆 I-cache 反更慢)。 - 【中】
std::endl强制 flush / iostream 在热路径 —std::endl每次一次writesyscall + iostream 慢(locale/锁)。换行用'\n',诊断输出走异步日志。 - 【中】未用 busy-poll / 内核旁路 — 标准内核栈固有几微秒 + 唤醒抖动限制延迟下限。超低延迟用 DPDK/Onload/AF_XDP 用户态轮询(注意是延迟换 CPU 功耗,非全栈适用)。
热路径体检报告(固定输出模板)
可直接截图发群。严格按此结构,逐条带
文件:行,不堆术语。
═══════════════════════════════════════════
热路径体检报告 / latency-audit
═══════════════════════════════════════════
审查范围:<文件/模块,圈出的热路径函数清单>
总评:🔴 危险 / 🟡 需整改 / 🟢 基本健康
致命 ✕N 高危 ✕N 中 ✕N 低 ✕N
一句判语:<例:撮合热路径上有一把 std::mutex + 一处 new,P99.9 极可能突刺,先拔锁去分配。>
───────────────────────────────────────────
【致命】热路径 std::mutex 锁争用
📍 order_engine.cpp:142 on_tick() 临界区
问题:每 tick 进 lock_guard 抢全局 g_book_mutex,争用陷 futex,
被抢占可等整个调度周期,直接炸 P99.9。
修复:该通道改 SPSC ring buffer;跨线程状态用 atomic
acquire/release;锁挪到冷路径。
验证:perf lock / perf record -e sched:sched_switch 看 futex_wait;
火焰图出现 __lll_lock_wait 即坐实。
【致命】NUMA 跨节点访问:交易线程在 node0,对象池在 node1
📍 engine_init.cpp:58 order_pool_ 在主线程构造,未 membind
问题:对象池 first-touch 落在 node0,但交易线程绑到 node1 核,
每次取单跨 socket 读远端内存,远端延迟近乎翻倍,尾延迟抬高。
修复:numactl --cpunodebind=1 --membind=1 启动,或在交易线程内
预触摸对象池;网卡中断亲和对齐到同一 node。
验证:numastat -p <pid> 看 numa_miss/other_node;perf c2c 看 remote HITM。
【高危】false sharing:ring head/tail 同 cache line
📍 spsc_queue.hpp:30-32 head_ / tail_ 紧邻无 padding
问题:生产者写 tail_、消费者写 head_,落同一 64B line,
MESI ping-pong 抬高尾延迟方差。
修复:head_ / tail_ 各 alignas(64) 隔到独立 cache line。
验证:perf c2c record/report 看 HITM(remote hit modified)。
【中】std::atomic 默认 seq_cst
📍 spsc_queue.hpp:45 tail_.store(next) 未指定 memory_order
问题:seq_cst store 在 x86 插 mfence,每 tick 累积开销。
修复:发布用 release store,消费用 acquire load。
验证:反汇编看热路径是否有多余 mfence。
───────────────────────────────────────────
⚠ 本报告为静态工程审查,只标嫌疑、不下延迟判决。
请用 perf 实测 P99/P99.9、perf c2c 验 false sharing、
numastat 验 NUMA 落点、perf stat 看 TLB miss、
火焰图定位真实热点后再优化——优化由 profiling 数据驱动,
本报告不承诺任何具体微秒数字。
═══════════════════════════════════════════
无命中时如实写「本轮未发现热路径级延迟坑」,并提示仍应实测验证,不硬凑条目。
严重度定义
| 严重度 | 含义 | 处置 |
|---|---|---|
| 🔴 致命(fatal) | 热路径上必然引入不可预测的 μs~ms 级尾延迟尖刺(锁争用、热路径分配、同步落盘、阻塞 I/O、Nagle、NUMA 跨节点、持 GIL),实盘直接吃滑点/丢成交 | 上线前必须修 |
| 🟠 高危(high) | 显著抬高均值或尾延迟方差(false sharing、cache miss 结构、TLB miss、热路径拷贝/格式化、cv 阻塞、缺批量、不绑核) | 强烈建议修,实测确认后优先处理 |
| 🟡 中(medium) | 累积可观但单次有限,或取舍型(seq_cst、未内联、分支预测、对齐、碎片、忙等退避) | 结合 profiling 评估收益再改 |
| ⚪ 低(low) | 风格/微优化,收益需实测佐证 | 可选,别为它牺牲可读性 |
严重度是先验排序,不是实测结论。某条「致命」若 profiling 证明根本不在瓶颈上,就该降级——永远以 perf 数据为准。
重要说明
- 这是工程审查工具,不是 profiler。 它基于代码模式找「延迟嫌疑」,真正的瓶颈必须用
perf/perf c2c/perf lock/numastat/perf stat -e dTLB-load-misses/ 火焰图 /cachegrind在真实负载下实测确认。代码看着像坑 ≠ 它就是你的瓶颈。 - 不替代 benchmark。 任何「修复」都要 A/B 实测(改前改后对比 P50/P99/P99.9),用数据说话,不靠直觉。
- 不承诺具体延迟数字。 本 skill 永远不会说「这能跑到 X 微秒」「修完省 Y ns」——延迟取决于硬件、内核参数、NUMA 拓扑、网卡、负载,只有你的实测说了算。
- 只在热路径较真,冷路径放行。 配置加载、日志落盘、对账、启动初始化里的 mutex / 分配 / 格式化都正常,别误报成致命。
- 修复有取舍成本。 busy-poll 烧 CPU 功耗、绑核占用整核、NUMA 亲和限制部署灵活性、huge page 需预留与运维配置、内核旁路抬高运维复杂度、零分配增加代码复杂度、
gil_scoped_release需保证不碰 Python 对象——本报告给方向,落地与否由你按 ROI 和实测收益定夺。 - 优先成熟轮子。 SPSC/MPMC 队列(moodycamel、Disruptor 思路)、flat hash(abseil、robin_hood)、异步日志(spdlog async、Quill、NanoLog)、JSON(simdjson)、分配器(jemalloc/tcmalloc/mimalloc)、NUMA 工具(libnuma/numactl)都有久经实盘的实现,优先复用而非手搓。
完整 40+ 条陷阱明细(每条含原理 / 检测手段 / 修复方案,含 NUMA 亲和、TLB/huge page、PyBind11 GIL 专条)见 references/latency-pitfalls.md。
What ships with it: 1 file
62.2 KB alongside SKILL.md
references/
- latency-pitfalls.md62.2 KB