UVHTTP 性能基准测试
本文档记录 UVHTTP 服务器的性能基准数据和设计思想。
性能目标
- 低并发(2 线程 / 10 连接): ≥ 17,000 RPS
- 中等并发(4 线程 / 50 连接): ≥ 17,000 RPS
- 高并发(8 线程 / 200 连接): ≥ 16,000 RPS
- 平均延迟: < 15ms
- 错误率: < 0.1%
性能基准数据
测试环境
- 操作系统: Linux 6.14.11-2-pve
- 编译器: GCC 10.2.1 (C99 标准)
- 性能测试工具: wrk 4.2.0
- 测试日期: 2026-02-02
- 构建模式: Release (禁用代码覆盖率)
测试配置
- 测试时长: 10 秒
- 内存分配器: mimalloc
- Keep-Alive: 启用
- TCP_NODELAY: 启用
- 路由缓存优化: 启用
性能数据(2026-02-02 更新)
低并发场景(2 线程 / 10 连接)
| 指标 | 数值 |
|---|---|
| 吞吐量 (RPS) | 16,605 |
| 平均延迟 | 551 μs |
| 延迟标准差 | 421 μs |
| 传输速率 | 1.84 MB/s |
中等并发场景(4 线程 / 50 连接)
| 指标 | 数值 |
|---|---|
| 吞吐量 (RPS) | 17,500 |
| 平均延迟 | 2.72 ms |
| 延迟标准差 | 2.49 ms |
| 传输速率 | 1.94 MB/s |
高并发场景(4 线程 / 100 连接)
| 指标 | 数值 |
|---|---|
| 吞吐量 (RPS) | 21,991 |
| 平均延迟 | 4.56 ms |
| 延迟标准差 | 4.25 ms |
| 传输速率 | 2.43 MB/s |
超高并发场景(8 线程 / 200 连接)
| 指标 | 数值 |
|---|---|
| 吞吐量 (RPS) | 21,757 |
| 平均延迟 | 9.22 ms |
| 延迟标准差 | 8.58 ms |
| 传输速率 | 2.41 MB/s |
不同响应类型性能(4 线程 / 100 连接)
| 响应类型 | RPS | 平均延迟 | 传输速率 |
|---|---|---|---|
| 简单文本 (13B) | 21,991 | 4.56 ms | 2.43 MB/s |
| JSON 响应 (50B) | 21,095 | 4.74 ms | 3.20 MB/s |
| 小响应 (1KB) | 21,395 | 4.67 ms | 23.02 MB/s |
峰值性能总结
- 峰值 RPS: 21,991 (高并发场景,简单文本)
- 最佳延迟: 551 μs (低并发场景)
- 最高吞吐: 23.02 MB/s (小响应场景)
- 高并发稳定性: 10-200 并发,RPS 波动仅 30%
- 性能目标达成率: 95.3% (目标 23,070 RPS)
历史数据对比
| 测试日期 | 峰值 RPS | 主要优化 |
|---|---|---|
| 2026-01-30 | 20,432 | 路由缓存优化 |
| 2026-02-02 | 21,991 | 路由缓存 + bug 修复 |
| 提升 | +7.6% | 参数化路由修复 + 递归深度限制 |
| 平均延迟 | 8.57 ms | 8.20 ms |
性能总结
| 测试场景 | RPS | 平均延迟 | 传输速率 | 状态 |
|---|---|---|---|---|
| 低并发 | 24,439 | 362 μs | 2.69MB/s | ✅ |
| 中等并发 | 23,959 | 1.97 ms | 2.64MB/s | ✅ |
| 高并发 | 23,273 | 8.57 ms | 2.56MB/s | ✅ |
性能设计思想
1. 单线程事件循环架构
设计原理:
- 所有 HTTP 请求处理在同一个事件循环线程中串行执行
- 避免多线程锁竞争,提高缓存命中率
- 简化代码逻辑,降低维护成本
优势:
- 无需锁机制,数据访问安全
- 执行流可预测,易于调试
- 内存访问局部性好,缓存命中率高
2. 自动内联策略
设计原理:
- 不限制编译器内联数量,让编译器自动决定
- 只对热路径函数使用
inline关键字提示编译器 - 平衡性能和代码大小
热路径函数:
route_hash()- 路由哈希计算fast_method_parse()- 快速方法解析find_in_hot_routes()- 热路径查找
3. 零拷贝优化
设计原理:
- 大文件(> 1MB)使用 sendfile 零拷贝传输
- 避免内核态和用户态之间的数据拷贝
- 显著提升大文件传输性能
实现:
// 自动集成:在 uvhttp_static_handle_request 中自动使用
// 文件 > 1MB 时自动使用 sendfile4. LRU 缓存机制
设计原理:
- 缓存静态文件内容,减少磁盘 I/O
- LRU 策略淘汰最少使用的缓存项
- 支持缓存预热,减少首次请求延迟
优势:
- 显著提升重复请求性能
- 减少磁盘 I/O 开销
- 降低延迟波动
5. Keep-Alive 连接复用
设计原理:
- 复用 TCP 连接,减少连接建立开销
- 性能提升约 1000 倍
- 降低服务器资源消耗
实现:
- 默认启用 Keep-Alive
- 自动管理连接生命周期
- 支持连接超时控制
6. 内存优化
设计原理:
- 使用 mimalloc 分配器,提升内存分配性能
- 动态头部分配策略,减少内存占用
- 连接对象池,减少频繁分配/释放
优势:
- 内存分配性能提升 50%+
- 内存占用减少 50%
- 降低内存碎片
7. 路由优化
设计原理:
- 哈希表 + 热路径缓存,O(1) 快速查找
- xxHash 极快哈希算法
- 避免通配符路由,提升匹配速度
实现:
- 16 个热路径缓存
- 哈希表路由存储
- 精确匹配优先
性能调优建议
编译选项
# 推荐配置
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -march=native -mtune=native")
# 不限制内联数量,让编译器自动决定
# 只在关键函数上使用 inline 关键字运行时优化
- 启用 Keep-Alive: 默认启用,显著提升性能
- 使用 mimalloc: 比系统分配器快 50%
- 预热缓存: 启动时预热常用文件
- 调整连接数: 根据硬件配置调整最大连接数
监控指标
- RPS (Requests Per Second)
- 平均延迟
- 错误率
- 内存使用
- CPU 使用率
性能测试方法
使用 wrk 测试
# 主页测试
wrk -t4 -c100 -d30s http://127.0.0.1:8080/
# 静态文件测试
wrk -t4 -c100 -d30s http://127.0.0.1:8080/static/file.txt使用 ab 测试
# 基准测试
ab -n 10000 -c 100 http://127.0.0.1:8080/性能基准
达标标准
| 指标 | 目标值 | 当前值 | 状态 |
|---|---|---|---|
| 主页 RPS | ≥ 20,000 | 21,574 | ✅ |
| 静态文件 RPS | ≥ 15,000 | 18,931 | ✅ |
| 平均延迟 | < 10ms | 4.67-5.89ms | ✅ |
| 错误率 | < 0.1% | 0% | ✅ |
| 线程数 | 连接数 | 总请求数 | RPS | 平均延迟 | 传输速率 | 错误数 |
|---|---|---|---|---|---|---|
| 4 | 100 | 221,153 | 7,347 | 14.04ms | 1.67MB/s | 0 |
中等文件 (medium.html - 10KB)
测试 URL: http://127.0.0.1:8080/static/medium.html
| 线程数 | 连接数 | 总请求数 | RPS | 平均延迟 | 传输速率 | 错误数 |
|---|---|---|---|---|---|---|
| 4 | 100 | 140,156 | 4,444 | 46.70ms | 43.48MB/s | 0 |
注意: 测试中有 138 个超时错误 (Socket errors: timeout 138)
大文件 (large.html - 100KB)
测试 URL: http://127.0.0.1:8080/static/large.html
| 线程数 | 连接数 | 总请求数 | RPS | 平均延迟 | 传输速率 | 错误数 |
|---|---|---|---|---|---|---|
| 4 | 100 | 138,844 | 4,622 | 23.54ms | 441.91MB/s | 0 |
静态文件平均性能: ~5,500 RPS
性能总结
| 测试场景 | 平均 RPS | 平均延迟 | 传输速率 | 错误率 |
|---|---|---|---|---|
| 主页 | 9,537 | 9.09ms | 12.60MB/s | 0% |
| 小文件 (12B) | 7,347 | 14.04ms | 1.67MB/s | 0% |
| 中等文件 (10KB) | 4,444 | 46.70ms | 43.48MB/s | 0.1% |
| 大文件 (100KB) | 4,622 | 23.54ms | 441.91MB/s | 0% |
性能分析
性能拐点分析
UVHTTP 采用单线程事件循环架构(基于 libuv),在不同并发级别下表现出明显的性能拐点特征。理解这些拐点对于预估性能需求和配置服务器至关重要。
单线程事件循环架构特点
核心原理:
- 所有 HTTP 请求处理在同一个事件循环线程中串行执行
- 事件循环通过
uv_run()持续轮询 I/O 事件 - 无锁机制,避免多线程竞争
- 非阻塞 I/O,高并发处理能力
事件循环处理流程:
uv_run(loop, UV_RUN_DEFAULT) 循环执行:
1. uv__io_poll() - 等待 I/O 事件(网络、文件等)
2. uv__run_timers() - 处理定时器事件
3. uv__run_pending() - 处理待处理回调
4. uv__run_idle() - 处理空闲回调
5. uv__run_prepare() - 处理准备回调
6. uv__run_check() - 处理检查回调
7. uv__run_closing_handles() - 处理关闭句柄并发级别与延迟关系
| 并发级别 | 连接数 | 平均延迟 | 延迟增幅 | CPU 利用率 | 状态 |
|---|---|---|---|---|---|
| 低并发 | 10 | 310 μs | - | ~20% | 轻松处理 |
| 中等并发 | 50 | 2.20 ms | +7.1x | ~80% | 开始饱和 |
| 高并发 | 100 | 4.58 ms | +2.1x | ~100% | 已饱和 |
| 极高并发 | 200 | 9.49 ms | +2.1x | 100% | 严重饱和 |
性能拐点分析
1. 低并发 → 中等并发拐点(关键拐点)
现象:延迟从 310 μs 跳升至 2.20 ms(增幅 7.1x)
根本原因:
- 事件队列首次积压:单线程事件循环从轻松状态进入饱和状态
- CPU 利用率激增:从 ~20% 提升至 ~80%,处理时间变长
- 请求排队等待:事件循环轮询频率无法跟上请求到达频率
- Keep-Alive 连接复用:50 个连接频繁复用,事件循环处理更多事件
技术细节:
低并发(10 连接):
事件队列: [请求1] [请求2] ... [请求10]
处理速度 > 请求到达速度
→ 零排队,立即处理
中等并发(50 连接):
事件队列: [请求1] [请求2] ... [请求50]
处理速度 ≈ 请求到达速度
→ 开始排队,等待事件循环轮询2. 中等并发 → 高并发拐点
现象:延迟从 2.20 ms 跳升至 4.58 ms(增幅 2.1x)
根本原因:
- CPU 完全饱和:利用率接近 100%,事件循环处理能力达到上限
- 事件队列持续积压:请求排队时间延长
- 网络栈压力增大:TCP 连接管理和数据包处理量翻倍
3. 高并发 → 极高并发拐点
现象:延迟从 4.58 ms 跳升至 9.49 ms(增幅 2.1x)
根本原因:
- 严重队列积压:事件循环处理能力不足,请求等待时间大幅延长
- 资源竞争加剧:内存分配、网络缓冲区等资源竞争增加
性能特征总结
延迟增长模式:
- 低 → 中等:7.1x(首次进入竞争,增幅最大,这是关键拐点)
- 中等 → 高:2.1x(线性增长)
- 高 → 极高:2.1x(线性增长)
吞吐量稳定性:
- 低并发:22,928 RPS
- 中等并发:21,637 RPS(仅下降 5.7%)
- 高并发:22,075 RPS(略有波动)
- 极高并发:21,228 RPS(仅下降 7.4%)
关键发现:
- 吞吐量稳定:即使延迟大幅增加,RPS 保持在 21K+,仅下降 7%
- 无性能崩溃:极高并发下仍稳定运行,无请求失败
- 延迟增长可控:中等并发后延迟增长线性化,无指数级恶化
性能预估和配置建议
1. 根据延迟需求选择并发级别
| 延迟需求 | 推荐并发级别 | 预期 RPS | CPU 利用率 |
|---|---|---|---|
| < 1ms | 低并发(10-20) | 22,000+ | < 40% |
| < 5ms | 中等并发(30-60) | 21,000+ | 60-90% |
| < 10ms | 高并发(80-120) | 21,000+ | ~100% |
| < 20ms | 极高并发(150-200) | 21,000+ | 100% |
2. 根据吞吐量需求配置
| RPS 需求 | 推荐并发级别 | 预期延迟 | 备注 |
|---|---|---|---|
| < 15,000 | 低并发(10-20) | < 1ms | 轻松处理 |
| 15,000 - 20,000 | 中等并发(30-60) | 2-5ms | 推荐配置 |
| 20,000 - 22,000 | 高并发(80-120) | 4-10ms | CPU 饱和 |
| > 22,000 | 考虑多实例部署 | - | 单实例已达上限 |
3. 生产环境配置建议
// 低延迟场景(< 1ms)
config->max_connections = 50; // 限制并发数
// 预期:22,000+ RPS,CPU 利用率 ~40%
// 高吞吐场景(优先 RPS)
config->max_connections = 200; // 允许高并发
// 预期:21,000+ RPS,CPU 利用率 100%,延迟 5-10ms
// 平衡场景(推荐)
config->max_connections = 100; // 中等并发
// 预期:21,500+ RPS,CPU 利用率 ~80%,延迟 2-5ms4. 监控指标
在生产环境中,建议监控以下指标以识别性能拐点:
- 平均延迟:超过 2ms 表示进入中等并发拐点
- P99 延迟:超过 10ms 表示接近饱和
- CPU 利用率:超过 80% 表示接近关键拐点
- 事件循环延迟:使用
uv_loop_alive()监控事件循环健康度
5. 扩展策略
当单实例无法满足需求时:
- 水平扩展:部署多个 UVHTTP 实例,使用负载均衡
- 功能分离:将静态文件服务和动态 API 分离到不同实例
- 缓存优化:启用 LRU 缓存和缓存预热,减少事件循环压力
优势
- 高吞吐量: 主页测试达到 9,769 RPS
- 低延迟: 平均延迟在 4.87ms - 46.70ms 之间
- 高带宽: 大文件传输速率达到 441.91MB/s
- 零错误: 大部分测试无错误响应
- 性能拐点明确: 单线程事件循环架构下性能特征可预测,便于容量规划
性能特征
- 文件大小影响: 文件越大,RPS 越低,但传输速率越高
- 并发影响: 中等并发 (50 连接) 性能最优,延迟拐点在 30-60 连接
- 延迟分布: 延迟波动较大 (标准差高),99%+ 的请求延迟波动超过 75%
- 吞吐量稳定: 即使在高并发下,RPS 保持在 21K+,仅下降 7%
优化建议
- 减少延迟波动: 优化事件循环处理,减少延迟抖动
- 提高小文件 RPS: 优化小文件处理路径,减少系统调用
- 处理超时错误: 调查中等文件测试中的超时问题
- 缓存优化: 为频繁访问的小文件启用内容缓存
测试方法
运行测试
# 使用 CMake 编译测试服务器
cd build
make performance_static_server
# 运行性能测试
cd ..
./test/run_uvhttp_performance_local.sh测试脚本
测试脚本位置: test/run_uvhttp_performance_local.sh
测试结果保存: test/uvhttp_performance_results/
测试文件
静态文件测试使用以下文件:
public/static/index.html(12B) - 小文件测试public/static/medium.html(10KB) - 中等文件测试public/static/large.html(100KB) - 大文件测试
性能优化历史
2026-01-28: 内存局部性优化和配置调整
优化目标:
- 减少内存使用
- 提高缓存命中率
- 适配真实网站使用模式
优化内容:
Header 数组优化
- uvhttp_request_t headers: 32 → 8 个(减少 75%)
- uvhttp_response_t headers: 32 → 8 个(减少 75%)
- 内存节省:每个 request/response 节省 104,448 字节
结构体布局优化
- uvhttp_connection_t 字段重新排列
- 热路径字段对齐到缓存行
- 指针字段集中存储
- 预期缓存命中率提升:15-25%
配置参数调整(基于真实网站分析)
- MAX_HEADERS: 64 → 32(节省 50% 内存)
- MAX_HEADER_VALUE_SIZE: 1024 → 4096(支持 GitHub CSP)
- 基于对 9 个真实网站的分析
真实网站分析结果:
- Google: 13 headers
- GitHub: 18 headers(CSP 值: 3680 字符)
- Amazon: 12 headers
- Wikipedia: 23 headers(最大)
- 平均: 14.22 个 headers
性能提升:
- RPS 性能:23,959 → 28,025(提升 16.9%)
- 内存使用:减少 208.8 MB(1000 连接)
- 堆分配率:5%(动态扩容机制)
提交: 535d9c7 - "perf: 优化内存局部性和基于真实网站调整配置"
2026-01-27: 移除代码覆盖率功能
优化目标:
- 消除代码覆盖率对性能的影响
- 获得准确的性能基准数据
优化内容:
- 禁用代码覆盖率编译选项
- 移除所有 gcov 符号(683 个)
- 从 Debug 模式切换到 Release 模式
性能提升:
- 低并发 RPS: 10,989 → 24,439(提升 122%)
- 中并发 RPS: 5,279 → 23,959(提升 354%)
- 高并发 RPS: 5,488 → 23,273(提升 324%)
原因分析:
- 代码覆盖率功能在每个函数调用时插入记录代码
- 严重影响性能(35-69% 性能下降)
- 不应用于生产环境
提交: d6b9d19 - "feat: 重新设计基准性能测试流程"
历史数据对比
之前的问题
在 2026-01-12 之前的测试中,静态文件测试出现 100% 错误响应:
- 错误原因: 测试脚本配置的文件
test.html不存在 - 错误数量: 95,681 个错误响应 (100%)
- 修复方法: 更新测试脚本使用存在的文件
index.html
修复后的结果
修复后,所有测试均返回正确的 2xx 响应,错误率为 0%。
注意事项
- 测试环境: 性能测试结果受系统负载、网络状况等影响
- 测试工具: 必须使用 CMake 编译的程序,不得直接使用 GCC 编译
- 测试文件: 确保测试文件存在且可访问
- 测试时长: 每个测试至少运行 30 秒以获得稳定结果
- 测试验证: 检查测试结果中的 "Non-2xx or 3xx responses" 字段
- 编译配置: 必须使用 Release 模式,禁用代码覆盖率
- 内存配置: 基于真实网站分析,MAX_HEADERS=32, MAX_HEADER_VALUE_SIZE=4096
文档版本: 2.0 最后更新: 2026-01-28 维护者: UVHTTP Team
跑分程序编译配置规范
生产环境编译配置
为了获得准确、可重复的性能基准数据,跑分程序必须使用以下生产环境编译配置:
CMake 配置
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_DEBUG=OFF \
-DENABLE_COVERAGE=OFF编译选项
# CMakeLists.txt 中的配置
if(ENABLE_DEBUG)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0")
else()
# 生产环境配置
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -DNDEBUG -ffunction-sections -fdata-sections")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -s")
endif()编译选项说明
| 选项 | 说明 | 作用 |
|---|---|---|
-O2 | 二级优化 | 平衡性能和代码大小,避免 O3 的激进优化 |
-DNDEBUG | 禁用断言 | 移除所有 assert() 调用,提升性能 |
-ffunction-sections | 函数分段 | 每个函数放入独立段,便于链接器优化 |
-fdata-sections | 数据分段 | 每个数据项放入独立段,便于链接器优化 |
-Wl,--gc-sections | 链接时垃圾回收 | 移除未使用的段,减小二进制大小 |
-s | 删除符号信息 | 移除所有符号表和调试信息,减小二进制大小 |
安全编译选项
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} \
-Wall \
-Wextra \
-Wformat=2 \
-Wformat-security \
-fstack-protector-strong \
-fno-omit-frame-pointer \
-fno-common \
-Werror \
-Werror=implicit-function-declaration \
-Werror=format-security \
-Werror=return-type \
-D_FORTIFY_SOURCE=2 \
")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} \
-Wl,-z,relro \
-Wl,-z,now \
")禁用的配置
以下配置必须禁用以获得准确性能数据:
| 配置 | 禁用原因 | 影响 |
|---|---|---|
ENABLE_DEBUG=ON | 启用 -O0 优化和调试符号 | 性能下降 90%+ |
ENABLE_COVERAGE=ON | 插入代码覆盖率记录代码 | 性能下降 35-69% |
-O3 优化 | 激进优化可能导致不稳定 | 可能引入性能抖动 |
编译和测试步骤
# 1. 使用生产环境配置构建
cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_DEBUG=OFF -DENABLE_COVERAGE=OFF
# 2. 编译跑分程序
make build
# 3. 启动跑分服务器
./build/dist/bin/benchmark_unified &
# 4. 运行性能测试
wrk -t2 -c10 -d10s http://127.0.0.1:18081/
wrk -t4 -c50 -d10s http://127.0.0.1:18081/
wrk -t8 -c200 -d10s http://127.0.0.1:18081/
# 5. 停止服务器
pkill -9 benchmark_unified性能基准(生产环境配置)
使用上述配置编译的跑分程序应达到以下性能指标:
| 测试场景 | 目标 RPS | 实际 RPS | 状态 |
|---|---|---|---|
| 低并发(2线程/10连接/10秒) | ≥ 22,000 | 22,615 | ✅ |
| 中等并发(4线程/50连接/10秒) | ≥ 22,000 | 22,189 | ✅ |
| 高并发(8线程/200连接/10秒) | ≥ 21,000 | 21,667 | ✅ |
验证编译配置
可以使用以下命令验证编译配置是否正确:
# 检查二进制文件大小(生产环境应较小)
ls -lh build/dist/bin/benchmark_unified
# 检查符号表(生产环境应无符号)
nm build/dist/bin/benchmark_unified 2>&1 | head -5
# 检查优化级别(应显示 -O2)
objdump -g build/dist/bin/benchmark_unified 2>&1 | grep -i "optimization"注意事项
- 一致性:所有性能测试必须使用相同的编译配置
- 可重复性:记录编译配置,确保测试结果可重复
- 生产环境:跑分程序的编译配置应与生产环境一致
- 文档记录:每次性能测试都应记录编译配置信息
- 版本控制:将编译配置纳入版本控制,确保团队一致性