# QUIC 性能测试修复报告 ## 问题描述 在 TCP 与 QUIC 性能对比实验中,QUIC 程序存在严重的性能问题: | 协议 | 吞吐量 (修复前) | |------|----------------| | TCP | ~2200 MB/s | | QUIC | ~6 MB/s | QUIC 吞吐量仅为 TCP 的 **0.3%**,这明显不正常。 ## 问题分析 ### 问题 1:超时处理导致性能低下 **原代码 (quic_perf_client.c 和 quic_perf_server.c):** ```c int64_t timeout_ns = quiche_conn_timeout_as_nanos(conn); if (timeout_ns > 0 && !has_outgoing && !finished_sending) { struct timespec ts; ts.tv_sec = timeout_ns / 1000000000; ts.tv_nsec = timeout_ns % 1000000000; nanosleep(&ts, NULL); } ``` **问题:** `quiche_conn_timeout_as_nanos()` 返回的是连接空闲超时时间(配置为 10 秒),而不是下一次需要处理事件的时间。这导致程序在没有数据发送时会等待很长时间,严重降低了事件循环的响应速度。 ### 问题 2:客户端过早关闭连接 原代码中客户端发送完数据后只是简单地 `usleep(1000)` 然后继续循环,没有正确处理连接关闭逻辑。 ## 修复方案 ### 修复 1:优化轮询间隔 **quic_perf_server.c (第 186-191 行):** ```c quiche_conn_on_timeout(conn); // 使用更短的轮询间隔以提高响应速度 if (!has_outgoing) { usleep(100); // 100微秒轮询 } ``` **quic_perf_client.c (第 125-130 行):** ```c quiche_conn_on_timeout(conn); // 使用更短的轮询间隔以提高响应速度 if (!has_outgoing && !finished_sending) { usleep(100); // 100微秒轮询 } ``` ### 修复 2:简化客户端发送完成后的处理 ```c if (finished_sending && !has_outgoing) { usleep(1000); } ``` ## 修复结果 | 协议 | 修复前 | 修复后 | 提升 | |------|--------|--------|------| | TCP | ~2200 MB/s | ~1400 MB/s | - | | QUIC | ~6 MB/s | ~92 MB/s | **15倍** | ## 测试命令 ### 正常网络环境测试 ```bash # TCP 测试 ./tcp_perf_server & sleep 1 ./tcp_perf_client wait # QUIC 测试 ./quic_perf_server & sleep 1 ./quic_perf_client sleep 3 ``` ### 丢包环境测试 (5%) ```bash # 添加 5% 丢包 sudo tc qdisc add dev lo root netem loss 5% # 运行 TCP 和 QUIC 测试... # 清除规则 sudo tc qdisc del dev lo root ``` ### 延迟环境测试 (100ms) ```bash # 添加 100ms 延迟 sudo tc qdisc add dev lo root netem delay 100ms # 运行 TCP 和 QUIC 测试... # 清除规则 sudo tc qdisc del dev lo root ``` ### 检查和清除 tc 规则 ```bash # 查看当前规则 tc qdisc show dev lo # 清除所有规则 sudo tc qdisc del dev lo root ``` --- ## 为什么 QUIC 吞吐量仍然远小于 TCP? 修复后 QUIC 达到 ~92 MB/s,而 TCP 达到 ~1400 MB/s,QUIC 仍然只有 TCP 的 **6.5%**。这是正常现象,原因如下: ### 1. 协议栈实现位置不同 | 协议 | 实现位置 | 特点 | |------|----------|------| | TCP | **内核空间** | 高度优化,零拷贝,硬件卸载 | | QUIC | **用户空间** | 需要系统调用,数据拷贝开销大 | TCP 在 Linux 内核中经过 30+ 年优化,支持: - **零拷贝 (Zero-copy)**:`sendfile()`, `splice()` 等 - **硬件卸载 (TSO/GSO)**:网卡直接分段 - **内核旁路优化**:减少上下文切换 ### 2. 加密开销 QUIC **强制加密**所有数据(使用 TLS 1.3),而本实验中的 TCP 是明文传输: ``` QUIC 数据路径: 应用数据 → TLS加密 → UDP封装 → 发送 TCP 数据路径: 应用数据 → TCP封装 → 发送 ``` 加密/解密操作消耗大量 CPU 资源。 ### 3. UDP vs TCP 的系统调用效率 | 操作 | TCP | QUIC (UDP) | |------|-----|------------| | 发送 100MB | 少量大块 write() | 大量小包 sendto() | | 系统调用次数 | 少 | 多 | TCP 可以一次 `write()` 发送大块数据,内核自动分段。QUIC 基于 UDP,每个数据包都需要单独的 `sendto()` 调用。 ### 4. 数据包大小限制 ```c #define MAX_DATAGRAM_SIZE 1350 // QUIC 单包最大载荷 ``` QUIC 受 UDP MTU 限制,每个包最多 ~1350 字节。发送 100MB 需要约 **77,000+ 个数据包**。 ### 5. 本地回环测试的特殊性 在 `localhost` 测试中,网络延迟几乎为零,TCP 的内核优化优势被放大。QUIC 的优势(如 0-RTT、连接迁移、多路复用)在这种环境下无法体现。 ### 6. QUIC 真正的优势场景 QUIC 设计目标不是追求极限吞吐量,而是在**真实网络环境**中提供更好的用户体验: | 场景 | QUIC 优势 | |------|-----------| | 高延迟网络 | 0-RTT 快速连接建立 | | 丢包环境 | 无队头阻塞,单流丢包不影响其他流 | | 移动网络 | 连接迁移,切换 WiFi/4G 不断连 | | 多路复用 | 单连接多流,避免 TCP 队头阻塞 | 从你的日志可以看到,在 **100ms 延迟**环境下: - TCP: 0.98 MB/s - QUIC: 2.41 MB/s **QUIC 在高延迟环境下性能是 TCP 的 2.5 倍!** --- ## 总结 | 环境 | TCP | QUIC | 胜者 | |------|-----|------|------| | 本地回环 (无延迟) | ~1400 MB/s | ~92 MB/s | TCP | | 100ms 延迟 | 0.98 MB/s | 2.41 MB/s | **QUIC** | **结论:** QUIC 在理想网络环境下吞吐量低于 TCP 是正常的,这是用户空间实现、强制加密等设计权衡的结果。但在真实的高延迟、高丢包网络环境中,QUIC 能够提供更好的性能和用户体验。