这篇文章主要介绍了在将capnp-rpc
从Lwt
移植到Eio
的过程中遇到的网络性能问题,并使用多种工具进行调查和分析,最终找到并解决了问题。主要内容如下:
- 问题描述:将
capnp-rpc
从Lwt
移植到Eio
后,运行基准测试发现新的Eio
版本速度非常慢,Lwt
版本使用 CPU 的时间为 98%,而Eio
版本大部分时间在睡眠。 使用的工具及介绍:
time
:可以显示程序在用户模式、内核模式和总挂钟时间上的花费,为问题提供了初步线索。eio-trace
:用于查看Eio
程序的行为,可运行命令并在窗口中显示跟踪,或保存跟踪以供以后检查。strace
:用于跟踪应用程序与 Linux 内核之间的交互,-tt -T
显示每个调用的开始时间和持续时间,-f
可跟踪所有派生的线程。bpftrace
:一种类似于awk
的脚本语言,可实时修补运行中的 Linux 内核,用于跟踪io_uring
操作。tcpdump
:用于捕获网络数据包,可在控制台或wireshark
中显示。ss
:显示套接字统计信息,ss -tin
显示所有 TCP 套接字的内部信息。offwaketime
:记录程序停止使用 CPU 的原因和导致其恢复的原因。magic-trace
:允许捕获 CPU 在某些事件发生之前所做的一切的短跟踪,使用 Intel Processor Trace 将所有控制流更改记录到环形缓冲区中。
具体调查过程及结果:
- 通过
eio-trace
跟踪发现每次测试循环迭代有 40ms 延迟,程序在第一次延迟中间会短暂唤醒然后继续睡眠,通过配置日志库可获取更多上下文信息,发现未缓冲输出导致两次单独写入而不是合并,这可能导致 40ms 延迟。 - 使用
strace
跟踪发现Eio
默认使用io_uring
机制提交工作到内核,超时后会唤醒futexes
并等待,切换到posix
后端后问题仍然存在,还发现Eio
在处理getaddrinfo
时会创建新的系统线程。 - 使用
bpftrace
跟踪io_uring
操作,发现io_uring_complete
事件的时间和结果,确认了 40ms 延迟。 - 使用
tcpdump
捕获网络数据包,看到应用程序两次写入之间 TCP 等待发送第二次数据,这是由于 Nagle 算法导致的延迟 ACK。 - 使用
ss
显示套接字统计信息,看到客户端套接字有 176 字节的队列等待确认。 - 使用
offwaketime
记录程序暂停和恢复的堆栈跟踪,发现 Eio 被网络接收的 TCP 数据唤醒,还发现 OCaml 的 tick 线程每 50ms 唤醒一次。 - 使用
magic-trace
获取更详细的函数级跟踪,看到Eio_posix.Flow.single_read
在读取时遇到EAGAIN
并切换到下一个纤维,以及tcp_write_xmit
中的 Nagle 算法。
- 通过
- 总结脚本及分析:编写
summary-posix.bt
脚本总结各种事件,包括应用程序的日志消息、套接字的读写和内核函数的调用,确认了问题的原因。 - 解决方案及效果:添加缓冲到
Eio
版本解决了问题,不仅消除了长延迟,还使 CPU 操作更快,新的Eio
版本速度从 511.963565 提升到 59653.451934 RPC 请求每秒。 - 结论:有很多工具可帮助理解程序运行缓慢的原因,每个工具都有其特点和用途,
bpftrace
非常有用,magic-trace
对于获取详细的函数级跟踪很有帮助,但很难直接指出 Nagle 算法与延迟 ACK 导致的问题。下一篇文章将调查 CPU 密集型问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。