Skip to content

Latest commit

 

History

History
219 lines (146 loc) · 11.2 KB

File metadata and controls

219 lines (146 loc) · 11.2 KB

从零写一个 KV:四个模型的性能工程对照

同一道题、同一份协议、同一把尺子。让四个模型用 Rust 从零实现一个内存 KV 存储服务, 再用统一压测套件量它们的性能工程能力。被测:Qwen3.8-Max、Claude Opus 4.8、Claude Fable 5、GPT-5.6 sol。


1. 任务前提

1.1 为什么是"从零写 KV + 压测"

我们要一个不靠 LLM 当裁判的能力对照。性能是客观标量,天然抗刷分——只要压测器和用例不交给被测方,跑出来的吞吐/延迟就是硬数字。

KV 服务是理想标的:功能一句话说得清(GET/SET/DEL/EXPIRE),但"写好"的优化空间极深,从网卡字节到哈希表再回网卡的每一段都有讲究。而且它天然语言中立——一套压测客户端能打所有实现。

1.2 一个必须先说清的边界

这套评测衡量的是"从零构建 + 性能优化"这一支能力,不是广义"代码质量"。

四家实现的正确性全部满分(27 条私有一致性用例全过,含协议边界、恶意帧、并发写、TTL 语义)。也就是说,"能不能写对"这件事四家没有区分度——全部的差距都在跑通之后的性能工程上。

所以本报告回答的是"各家性能强在哪、差在哪",而不是"谁综合最好"。后一个问题没有客观答案(见第 5 章)。

1.3 固定项

  • 语言固定 Rust(edition 2021,工具链 1.83)。跨语言性能差数十倍,锁语言才可比。
  • 协议固定:自造的 QKV 二进制协议(长度前缀 + opcode + LEB128 varint,四个命令)。自造是为了抗抄——没有现成实现可搬。
  • 依赖白名单:仅 std + libc,禁 async runtime、禁第三方 KV/哈希/分配器 crate,核心逻辑必须自写。这样才测得出真实的并发模型与数据结构选择。

2. 任务环境

2.1 agent 看到的初始环境

刻意精简,只给三样,不含任何优化提示:

  • PROMPT.md——任务书(写什么、交付什么、限制)
  • PROTOCOL.md——协议规范(行为以它为准)
  • smoke/——一组基础自测用例

题面明示协议语义(客户端会连续发多个请求、TCP 会任意分帧),但绝不提示"批量解析能提速"——这个优化断崖是主要区分点,说了就废了。

2.2 评测执行环境

每个评测 job 拉起一对全新容器,跑完销毁:

┌ 服务端容器 (4 核 / 3G / 断网) ┐       ┌ 压测端容器 (8 核 / 6G) ┐
│ 断网编译并运行被测 qkvd        │◄─────►│ 编译压测器,跑评测套件  │
│ 监听 :7777                     │ 共享   └─────────────────────────┘
└────────────────────────────────┘ net+pid 命名空间

三个关键设计:

  • 服务端断网编译——强制验证离线可编译,排除外网干扰
  • 压测端独占 8 核、与服务端不重叠——防止压测端自己成为瓶颈
  • 共享 pid 命名空间——压测端才能读到服务端进程的 RSS/CPU

规格由脚本常量固定,四家字面一致:服务端 4 核/3G/pids128/nofile65536,压测端 8 核/6G。全程串行,一次一个 job。

参考数据跑在 WSL2 上,--cpuset 经 Hyper-V 二次调度、超线程/turbo 不可控,故绝对值偏保守、相对结论可信。


3. 六个技术点

一句话的 KV,优化空间藏在"字节到哈希表再回字节"的每一段。拆成 6 个点,按对性能的影响权重排序:

权重 技术点 可优化的是什么
P0 写路径批处理 决定档位 一次 read 收到 N 条命令,是逐条 write 回,还是全部解析完、响应拼成一块一次 write。系统调用从 2N 降到 2。
P1 并发与分片锁 高 事件循环怎么用满多核;共享哈希表分几片降低锁竞争;worker 数怎么定。
P5 过载背压 中 请求涌入超过处理能力时,是无限吃进来撑爆缓冲,还是摘掉 EPOLLIN 主动限流。
P4 内存布局与回收 中 key/value 用 Box<[u8]>(精确) 还是 Vec<u8>(带冗余);value 能否 Arc 共享;缓冲用完是否收缩;过期怎么回收。
P3 哈希算法 中低 默认 SipHash(抗DoS但慢) vs 自写 FxHash(KV 短 key 场景快 3-5 倍)。
P2 零拷贝解析 低 解析时借用输入切片,还是每帧 to_vec() 拷一份。

权重是基于评测观察的判断估计,不是精确测量。P0/P1 合计约占区分度的七成。


4. 评测标准

4.1 六个测试组

两种压测范式,是六组最根本的分野:

组 范式 测什么 主测技术点
G1 极限读 闭环满速 100% GET,并发×批次网格取峰值 P0
G2 极限写 闭环满速 100% SET P0 + P1
G3 读写 50/50 闭环满速 混合 P0 + P1
G4 长程稳定性 开环定速 固定 4 万 QPS × 120s P4 + 单请求延迟
G5 负载延迟曲线 开环定速 2/4/8/16 万 QPS 四档 p99 P5(过载档)
G6 负载资源曲线 开环定速 同档位 RSS/CPU(与 G5 共用运行) P4 内存斜率

核心指标:批处理增益 = 批次 256 吞吐 ÷ 批次 1 吞吐。它是全套里最可靠的——比值型,分子分母同机同码,机器快慢被约掉。跨轮吞吐波动 ±55% 时它仍稳定分档。

4.2 数据分布(六组通用)

  • key 空间 20 万(前缀+8位序号),value 固定 16B
  • 访问分布:zipf 近似热点,90% 请求打在前 10% key 上
  • 完全确定性:种子硬编码,四家跑逐字节相同的负载,不是随机

4.3 三道有效性保护

  • 每个吞吐网格点重复 5 次取中位数(压制单次抖动)
  • 压测端 CPU 同步采样,峰值超 8 核×85% 标"不可信"(防止测到客户端上限)
  • 定速档 behind_schedule > 5% 标"该档无效"(防止定速没跟上)

4.4 结论强度分两档

  • 可强断言(换机器也稳):数量级差距、批处理增益分档、吞吐排名≠延迟排名。
  • 仅本配置成立:前三名精确次序(差 0.5-9% < 跨轮方差,统计不显著)、绝对 ops/s 值。对核数、批次上限敏感。

5. 各家评测表现

5.1 极限吞吐(三轮中位数,ops/s)

Qwen3.8-Max Opus 4.8 Fable 5 GPT-5.6 sol
极限读 G1 11.8M 10.5M 10.5M 4.5M
极限写 G2 12.2M 9.6M 8.7M 4.7M
读写 G3 10.2M 8.8M 9.1M 4.7M
批处理增益 99x 92x 98x 12x→52x(v2)

5.2 负载响应

Qwen3.8-Max Opus 4.8 Fable 5 GPT-5.6 sol
长程 p99 (G4) 217µs 183µs 191µs 241µs
8 万档 p99 (G5) 390µs 303µs 369µs 533µs
过载档 p99 (G5) 582µs 909µs 537µs 716µs
RSS 随负载 (G6) 30→34MB 24.7MB 27→28MB 34MB

5.3 六项名次

G1 G2 G3 G4 G5 G6 夺冠
Qwen3.8-Max 1 1 1 3 3 1 4
Opus 4.8 2 2 3 1 1 3 2
Fable 5 3 3 2 2 2 2 0
GPT-5.6 sol 4 4 4 4 4 4 0

5.4 核心结论:四家六项产生三个不同的"第一"

  • Qwen3.8-Max——吞吐三冠 + CPU 效率最高。客户端会批量发、在意机器成本时最优。
  • Opus 4.8——延迟最优 + 内存最省。常规一问一答、在意延迟时最优。
  • Fable 5——六项全第二、零短板、过载档尾延迟最稳。负载未知/怕过载雪崩时最优。
  • GPT-5.6 sol——曾落后一个数量级,v2 修入 writev 后收窄到 2.5 倍,但仍全项第四。

不存在绝对最优,单一指标必然选错——这是本评测最重要的方法论产出。想比高下,得先锁定场景("读多写少+在意成本"→ 看吞吐 → Qwen 赢),把权重前提摆到台面上,而不是合成一个假装客观的总分。


6. 各家技术点实现情况

四家架构高度同源(SO_REUSEPORT + 每 worker 独立 epoll + 分片 HashMap + 纯 std/libc),差距全在各技术点的具体取舍。以下为逐行源码归因。

P0 写路径批处理——决定档位

实现 做法 结果
Qwen/Opus/Fable 一次 read 解析全部命令,响应拼一整块一次写 增益 92-99x,千万级
GPT-5.6 sol 原版 每条命令单独一次 write(),libc 连 writev 都没声明 增益仅 12x,百万级(掉一个数量级)
sol v2 引入 writev 攒批,但不彻底 增益 52x,收窄到 2.5 倍

这是四家吞吐差 2.6-10 倍的头号原因。

P1 并发与分片锁

  • Qwen 1024 分片——写扩展最好,G2 领先 31%(分片价值只在写负载体现)。
  • Fable/Opus 256 分片——够用,锁竞争近零。
  • Opus 用 22 worker(=核数自适应)换低载延迟,但过载时调度抖动。
  • sol 仅 64 分片 + 全局 AtomicUsize 记账——分片不足 + 原子争用,8 worker 反而最慢最费 CPU。

P5 过载背压

  • Fable 5 是唯一真生效的背压——积压超 4MB 摘 EPOLLIN、降到 64KB 恢复,过载档 p99 最稳 537µs。
  • Qwen 部分生效(582µs)。
  • Opus 4.8 阈值设 16MB,对几字节的小响应形同虚设——低载延迟王,过载却塌到 909µs。
  • sol 的全局 512MB 上限是争用源不是背压(1231µs,v2 后 716µs)。

P4 内存布局与回收

  • Opus 4.8——Box<[u8]> 精确 + 空分片零预分配 + 缓冲自收缩,RSS 最省 24.7MB。
  • Fable 5——Box<[u8]> + Arc<[u8]> 共享 value + 增量轮扫过期,RSS 随负载几乎不涨(27→28MB)。
  • Qwen——Vec<u8>(带冗余) + HashMap 扩容不归还,RSS 随负载涨最多(30→34MB)。
  • sol——Vec + 每请求约 4 次堆分配,写负载 RSS 曾达 47MB。

P3 哈希算法

  • Fable/Opus——自写 FxHash,CPU 低。
  • Qwen——自己写了 fxhash64 却只用于选分片,内层 HashMap 仍用默认 SipHash(明显遗漏,拖累单请求延迟,这是它 G4 p99 偏高的直接原因)。
  • sol——双重 SipHash(选分片+内层各一次),纯浪费 CPU。

P2 零拷贝解析

  • Qwen——零拷贝 body 借用 + 延迟 compact,每帧无额外分配。
  • Fable——边界触发 copy_within,偶发 memmove。
  • Opus——每帧全量 revalidate。
  • sol——每帧 to_vec() 整帧复制,制造大量分配抖动。

一条贯穿全局的观察

Qwen 顶着两个短板(SipHash 未换、每轮扫全表)仍拿吞吐第一——因为它的优点全砸在最高权重的 P0/P1,缺点在中低权重的 P3/P5。而 Fable 5 是唯一每个技术点都不偷工的实现,所以六项全第二、零短板。这正是"综合工程最扎实"与"某场景性能最强"是两个不同问题的根源。


附:可复现

bash run-eval.sh                 # 四家 × 六组
bash run-eval.sh --rounds 3      # 三轮取中位数(更稳)

题面、四家产物(含 vendor 离线可编译)、压测引擎、评分脚本全部在本目录。跨机器对比须同机重跑四家基线——绝对值不可跨环境比较。