3、测量与基准:如何精确测量延迟?
做市系统里,延迟测量是个老生常谈却又容易翻车的话题。
我见过不少团队,优化了半天,结果发现测量方法本身就是错的。你想想看,如果连基准都不准,那优化方向很可能南辕北辙。
这一章,我们就来聊聊怎么把延迟测准。说白了,就是三个层次:硬件时间戳、内核旁路时间戳、以及内核路径追踪。
3.1 硬件时间戳:PTP 的玩法
先讲最准的——硬件时间戳。我个人习惯,只要条件允许,优先用 PTP(Precision Time Protocol)。
为什么?因为软件打时间戳,误差动辄几微秒。而 PTP 在网卡硬件层面打戳,精度能到纳秒级。
核心思路:网卡在报文进出物理层时,直接打上硬件时间戳。这个时间戳来自 PTP 同步后的时钟域,误差主要取决于晶振漂移和网络对称性。
我在项目中遇到过一个问题:两台机器用 PTP 同步后,时间戳偏差还是偶尔跳到 500 纳秒以上。排查了半天,发现是交换机上的 PTP 透明时钟没开对。嗯,这里要注意,PTP 的精度高度依赖网络设备的支持。
配置 PTP 的典型步骤:
# 安装 linuxptp 工具包
sudo apt-get install linuxptp
# 启动 ptp4l(主时钟模式)
sudo ptp4l -i eth0 -m -S
# 启动 phc2sys(将硬件时钟同步到系统时钟)
sudo phc2sys -s eth0 -c CLOCK_REALTIME -m
验证同步精度:
# 查看 PTP 偏移量
sudo pmc -u -b 0 'GET CURRENT_DATA_SET'
小技巧:用 ethtool -T eth0 查看网卡是否支持硬件时间戳。如果输出里没有 hardware-transmit 和 hardware-receive,那这网卡玩不了 PTP。
3.2 DPDK 的 rte_rdtsc():用户态的时间戳
如果你用 DPDK 做高频交易,那 rte_rdtsc() 应该是老朋友了。它直接读取 CPU 的 TSC 寄存器,开销极低——大概几十个 CPU 周期。
但这里有个坑:TSC 频率可能不稳定。我记得有一次,系统在睿频状态下,TSC 频率跳变了,导致我算出来的延迟忽高忽低。后来我强制锁频,才解决了这个问题。
使用示例:
#include <rte_cycles.h>
uint64_t start, end;
double latency_ns;
start = rte_rdtsc();
// 这里放你要测量的操作
end = rte_rdtsc();
// 转换为纳秒
latency_ns = (double)(end - start) / rte_get_tsc_hz() * 1e9;
警告:不要在生产环境里频繁调用 rte_rdtsc()。虽然它开销低,但每次调用都会刷新流水线。我建议只在关键路径上打戳,比如收包和发包的瞬间。
3.3 eBPF 程序追踪内核路径
有时候,我们需要知道报文在内核里到底走了哪些弯路。这时候 eBPF 就派上用场了。
eBPF 可以挂载到内核的各个函数入口和出口,精确记录时间戳。我常用它来追踪 __netif_receive_skb_core 和 ip_rcv 这些关键函数。
一个简单的 eBPF 程序示例:
#include <linux/bpf.h>
#include <bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32);
__type(value, u64);
} timestamps SEC(".maps");
SEC("kprobe/__netif_receive_skb_core")
int trace_entry(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid();
u64 ts = bpf_ktime_get_ns();
bpf_map_update_elem(×tamps, &pid, &ts, BPF_ANY);
return 0;
}
SEC("kretprobe/__netif_receive_skb_core")
int trace_exit(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid();
u64 *start = bpf_map_lookup_elem(×tamps, &pid);
if (start) {
u64 delta = bpf_ktime_get_ns() - *start;
bpf_printk("内核路径耗时: %lld ns\n", delta);
}
return 0;
}
避坑指南:我曾经用 eBPF 追踪 tcp_v4_rcv,结果发现每次测量结果都偏大。后来才意识到,eBPF 程序本身也会占用 CPU 时间。解决办法是:在测量代码里减去 eBPF 自身的开销,或者用 tracepoint 替代 kprobe,因为 tracepoint 的开销更小。
3.4 搭建延迟基准测试环境
好了,工具都齐了。怎么搭一个靠谱的基准测试环境?
我的建议是:用两台机器直连,中间不要经过交换机。一台发报文,一台收报文并打时间戳。
硬件清单:
- 两台服务器,CPU 支持 TSC 恒定频率(Intel 的 Invariant TSC)
- 支持 PTP 的网卡(比如 Intel X710、Mellanox ConnectX-5)
- 直连光纤或 DAC 线缆
软件配置:
- 关闭 CPU 睿频和节能模式(
cpupower frequency-set -g performance) - 关闭中断合并(
ethtool -C eth0 rx-usecs 0 tx-usecs 0) - 绑定网卡中断到特定 CPU 核心
测试流程:
# 发送端:以固定速率发送 UDP 报文
sockperf pp --tcp -i 192.168.1.2 -p 12345 -m 64 -t 10
# 接收端:记录每个报文的时间戳
sockperf sr --tcp -i 192.168.1.2 -p 12345 --time-stamp -m 64
个人经验:第一次跑基准测试时,先跑 100 万次,然后看延迟分布。不要只看平均值,P99 和 P99.9 才是关键。我曾经优化了半天平均值,结果 P99.9 反而变差了——因为优化手段引入了偶发的抖动。
3.5 知识体系总览
下面这张图,概括了本章的核心逻辑:
这张图展示了三个测量层次的关系。硬件时间戳最准,但依赖网卡支持。用户态时间戳灵活,但要注意 TSC 频率。内核追踪能帮你定位问题,但本身有开销。
实际项目中,我通常三管齐下:先用 eBPF 找出热点路径,然后用 rte_rdtsc() 做细粒度测量,最后用 PTP 做端到端验证。这样测出来的数据,才敢拿去指导优化。
最后说一句:测量本身也是一种开销。不要为了测量而测量,搞清楚你要优化什么,再选合适的测量工具。否则,你只是在制造数据,而不是在解决问题。
公众号:蓝海资料掘金营,微信deep3321