ef_vi必须预分配recv buffer,因其采用零拷贝DMA直写机制,未注册足够对齐buffer会导致静默丢包;需用posix_memalign分配、调用ef_vi_receive_init注册,并严格绑定CPU核与NUMA节点。

ef_vi 为什么必须预分配 recv buffer
ef_vi 不是 socket,它不帮你管理内存。网卡收到 UDP 包后,直接通过 DMA 写入你提前注册的 recv buffer,中间零拷贝、无内核介入。如果你没预先分配并注册足够数量的 buffer,ef_vi 就会丢包——不是报错,而是静默丢弃,连日志都不打。
常见错误现象:ef_vi_receive_get() 返回 NULL,但 ef_eventq_poll() 却持续返回 EF_EVENT_TYPE_RX 事件,说明包到了、buffer 却空了。
- 每个
recv queue的 buffer 数量建议 ≥ 1024,小包场景(如行情)建议 2048+,避免 burst 时耗尽 - buffer 必须用
posix_memalign()分配,对齐到 64 字节(X2522 要求),不能用new或malloc - 注册前需调用
ef_vi_receive_init()填充 buffer 描述符链表,漏掉这步等于没注册
UDP 接收必须轮询 eventq,不能 sleep 或 epoll 等待
ef_vi 是纯轮询模型。你得在 tight loop 里反复调用 ef_eventq_poll() 检查是否有 EF_EVENT_TYPE_RX,然后立刻用 ef_vi_receive_get() 取数据。任何阻塞(usleep、epoll_wait、甚至 sched_yield())都会引入微秒级抖动,直接破坏低延迟目标。
使用场景:行情接收线程必须绑定到独占 CPU 核(taskset -c 3 ./app),关闭该核的 timer 中断(echo 0 > /proc/sys/kernel/timer_migration),否则中断抢占会让 poll 延迟飙升。
立即学习“C++免费学习笔记(深入)”;
- 每次
ef_eventq_poll()最多处理 1 个事件,别指望批量;想提吞吐就靠提高 poll 频率(但注意 CPU 占用) - 收到包后,立刻调用
ef_vi_receive_return()把 buffer 归还队列,否则下一轮没 buffer 可用 - 不要在 poll 循环里做复杂解析——把 raw packet 指针和长度放进无锁队列,交给另一个线程处理
发送 UDP 一定要复用 frame,不能每包构造 header
ef_vi 发送走的是 ef_vi_transmit() + ef_vi_transmit_pio()(CTPIO)路径。X2522 的 CTPIO 要求你把整个以太网帧(L2 header + IP + UDP + payload)预先写死在内存里,发送时只传一个指针和长度。如果每次发包都动态拼 header,光是 memcpy 和 checksum 计算就吃掉 200+ ns。
性能影响:实测同一台机器直连,复用预填充 frame 可将单包发送延迟压到 ~960ns;动态构造则跳到 ~2.1μs,差了一倍多。
- 初始化阶段就用
memcpy把固定 header(含 src/dst MAC、IP、UDP port)写进一块对齐内存 - payload 部分用指针偏移方式填入,避免移动 header 数据
- UDP checksum 必须手动计算并写入 header,
ef_vi不代劳;用cksum工具验证初始值是否正确
为什么不能混用 onload 和 ef_vi 在同一个进程
onload 会劫持所有 socket 调用,并接管进程的内存管理、中断处理逻辑;而 ef_vi 要求完全独占 VI 资源、绕过一切内核调度。两者共存时,onload 的信号处理、timer 注册、甚至 fd 表操作都会干扰 ef_vi 的 eventq 轮询节奏,导致 ef_eventq_poll() 延迟毛刺从几十 ns 涨到几 μs。
容易踩的坑:你以为只链接了 libefvi.a 就安全了,但只要进程里加载了 libonload.so(比如某个第三方库隐式 dlopen),onload 就会自动激活——哪怕你一个 socket 没开。
- 启动前检查:
lsof -p $PID | grep onload,确认无相关 so 加载 - CMake 中显式设置
LINK_FLAGS "-Wl,--no-as-needed",防止 linker 自动拉入 onload - 绝对不要在同一个二进制里同时调用
socket()和ef_vi_open();功能隔离必须做到进程级


















