本文目录导读:

我们来详细探讨如何测试 tcp_autocorking(通常指 tcp_autocork 或相关的内核自动 corking 行为),这个测试的核心目标是验证 Linux 内核在处理小数据包写入时,是否会自动合并(cork)这些数据包,以减少网络传输中的小包数量,从而提高吞吐量并降低 CPU 开销。
tcp_autocorking 是一个相对新(约 2019 年加入,但在后续版本中持续优化)的内核特性,它允许内核在特定条件下自动执行 cork 操作,即使应用程序没有显式调用 TCP_CORK 或使用 sendfile() 等大块写入。
核心测试思路
测试 tcp_autocorking 的关键是比较开启和关闭该特性时,网络传输的行为差异,主要关注点:
- 发送的数据包数量:开启后,小包应被合并,导致发送的 TCP 段(Segments)数量减少。
- 系统调用次数:
write/send的调用次数不变,但实际发送的次数可能变化。 - 吞吐量:对于某些特定工作负载(如大量、小、高频率写入),开启后吞吐量应提升。
- 延迟:可能略有增加(因为要等待合并),但通常影响不大。
测试方法
你可以通过以下几种具体方法来测试。
使用 ss 和 netstat 观察统计信息
这是最直接的方法,在发送端和接收端运行。
步骤:
-
建立连接并模拟小包写入:
- 编写一个简单的程序(如 Python/TCP 客户端)不断向服务器发送很小的数据包(1 字节)。
- 确保服务器端接收。
-
观察发送端的 TCP 统计:
- 使用
ss -tiep或netstat -st查看segments(段)数量。 - 关键指标是
segments retransmitted和segments总数量除以写入的数据量,如果数据包合并得当,发送的 TCP 段数量应远小于写入的数据块数。
# 查看特定连接的统计 watch -n 1 "ss -tiep | grep <your-connection>" # 或者查看全局 TCP 段统计 netstat -s | grep -E "segments|packet"
- 对比测试: 分别测试
tcp_autocorking = 1(默认) 和tcp_autocorking = 0时的段数量。
- 使用
tcpdump / tshark 抓包分析
这是最精确的方法,可以直接看到网络上的数据包。
步骤:
-
在发送端或接收端捕获流量:
tcpdump -i any -w /tmp/test_autocork.pcap port 12345
-
运行测试程序(发送大量小数据包)。
-
分析 pcap 文件:
- 使用
tshark统计data.len(TCP payload size) 的分布。 autocorking生效,你应该会看到大部分数据包的有效载荷(payload)大于你发送的小块大小,你发送了 1000 次 1 字节的send(),正常情况下你可能看到 1000 个 1 字节的数据包(如果没合并),或者看到几百个合并后的较大数据包(如几十到上百字节)。- 可以使用
tshark -r test.pcap -T fields -e tcp.payload查看 payload 大小。 - 关键统计:
tshark -r test.pcap -T fields -e frame.len可以快速知道包的大小分布,小包(如小于 100 字节)的比例下降,大包(如 MSS,即最大段大小)的比例显著上升。
- 使用
-
对比测试:
- 关闭
autocorking:echo 0 > /proc/sys/net/ipv4/tcp_autocorking
- 重复抓包,你会发现关闭后,
data.len极大概率等于你发送的单次写入大小(1 字节),导致大量极小的ACK和数据包。
- 关闭
使用系统性能工具(perf, ftrace, eBPF)
可以追踪内核函数 tcp_push() 或 __tcp_transmit_skb() 的调用情况,但这通常需要较深的内核知识。
eBPF 示例(简单思路):
你可以写一个简单的 eBPF 程序来统计 tcp_sendmsg 被调用的次数和实际发送的 SKB(socket buffer)数量,当 autocorking 工作时,tcp_push 会延迟发送,将多个用户态写入合并成一个 SKB。
编写一个简单的性能测试程序(推荐)
这是一个非常有效且可控的测试方案。
程序逻辑(C 或 Python):
-
Client (发送端):
- 创建 TCP 连接。
- 循环执行 10000 次:
- 执行
write(sock, buffer, 1)(发送 1 字节)。 usleep(100)微秒睡眠,模拟真实的高频小包场景。
- 执行
- 记录总耗时和实际发送的数据量。
-
Server (接收端):
- 创建 TCP 监听套接字。
- 接收数据直到连接关闭。
- 记录接收到的总字节数和数据包数量(可以通过
recvfrom得到每次recv的字节数,但这不是直接的网络包数量,更精确的是监听tcpdump)。
测试对比:
-
开启 autocorking (默认):
- 启动服务器,运行客户端。
- 记录客户端耗时、服务器端收到的数据包数量(通过
tcpdump)。
-
关闭 autocorking:
echo 0 > /proc/sys/net/ipv4/tcp_autocorking- 重复测试。
预期结果:
| 特性 | 耗时 (秒) | 发送的 TCP 段数量 | 服务器端收到的包数量 |
|---|---|---|---|
tcp_autocork=1 |
较短 | 较少 (如 500) | 较少 (如 500) |
tcp_autocork=0 |
较长 | 较多 (如 9500) | 较多 (如 9500) |
为什么耗时变短? 因为合并后的数据包可以一次(或几次)进行 Nagle 算法合并、TSO(TCP分段卸载)等,减少了中断处理和内核协议栈的开销。
注意事项和陷阱
-
Nagle 算法的干扰:
tcp_autocorking是独立于 Nagle 算法 (TCP_NODELAY) 的。默认 Nagle 是开启的,Nagle 算法会等待接收 ACK 后再发送后续数据,为了隔离测试 autocorking,最好将两者结合或分别测试。- 如果测试发送端开启
TCP_NODELAY,则 Nagle 被禁止,autocorking成为主要的合并机制。 - 如果测试发送端不开
TCP_NODELAY,Nagle 也会合并小包,效果类似,但逻辑不同,建议测试时明确控制TCP_NODELAY。
- 如果测试发送端开启
-
发送速率和睡眠时间:
autocorking的生效依赖于时机,如果写入频率太快(没有睡眠),数据会立即被发送出去(称为push标志自动设置),如果写入之间睡眠足够长(如 10ms),则可能触发 corking 合并,你的测试程序中的usleep长度会影响合并效果,建议尝试 100us ~ 10ms 的范围。 -
内核版本: 确保你的 Linux 内核版本支持
tcp_autocorking(4.19+),较新版本 (5.x, 6.x) 行为可能有些微调。 -
对端接收能力: 接收窗口的大小会影响发送行为,如果接收窗口足够大,合并效果更好。
一个完整的 Python 测试脚本示例
import socket
import time
import sys
def test_autocork(host='127.0.0.1', port=12345, nagle_on=True, payload_size=1, iterations=10000, sleep_us=100):
# 1. 创建套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1 if not nagle_on else 0)
sock.connect((host, port))
buf = b'x' * payload_size
# 2. 计时并发送
start = time.time()
for i in range(iterations):
sock.send(buf)
if sleep_us > 0:
time.sleep(sleep_us / 1_000_000.0) # 微秒睡眠
end = time.time()
# 3. 关闭连接
sock.close()
elapsed = end - start
print(f"Nagle: {nagle_on}, 总花费: {elapsed:.3f}秒, 吞吐量: { (iterations * payload_size) / elapsed / 1024 / 1024:.2f} MB/s")
print(f"参考: 理论上如果未合并, 应发送 {iterations} 个包; 合并后包数 = 总数据量 / MSS")
if __name__ == '__main__':
host = sys.argv[1] if len(sys.argv) > 1 else '127.0.0.1'
port = int(sys.argv[2]) if len(sys.argv) > 2 else 12345
# 测试关闭 Nagle (只测 autocorking)
print("--- 测试 tcp_autocorking (关闭 Nagle) ---")
test_autocork(host, port, nagle_on=False) # 只依赖 autocorking
# 测试开启 Nagle
print("--- 测试 Nagle + autocorking ---")
test_autocork(host, port, nagle_on=True) # 两者都有
同时运行一个简单的接收服务器:
import socket
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 12345))
s.listen(1)
conn, addr = s.accept()
data = conn.recv(4096)
while data:
data = conn.recv(4096)
conn.close()
s.close()
总结测试步骤
- 抓包对比: 使用
tcpdump抓取关闭 Nagle 后的流量,对比autocorking=1和autocorking=0时数据包的大小分布。 - 性能对比: 使用上面的 Python 程序,对比相同的小负载,每次写入 1 字节,比较总耗时和吞吐量。
- 内核参数确认: 确保你测试时切换了
/proc/sys/net/ipv4/tcp_autocorking并观察效果。 - 注意网络环境: 最好在回环接口 (lo) 或同一台机器的虚拟机上进行测试,以排除网络拥塞的干扰。
通过以上方法,你可以清晰地量化 tcp_autocorking 是否工作以及效果如何,关键在于观察小包被合并为大包这一核心现象。