把 BadgerDB 移植到仓颉(三):重跑基准,三个结论翻了
内容提要
作者重跑BadgerDB仓颉移植版基准,推翻原有三条结论:并发写实际比Go慢约2.6倍而非1.3倍,原因是两侧规模相差1300倍;FFI memcmp因约70纳秒调用开销,在随机key下反比逐字节比较慢18倍;io_uring在小文件场景慢2–6倍,但大写入高并发时可快1.1–1.6倍。错误源于规模未对齐、输入分布不匹配及结论超出证据边界。
延伸解读
基准对比的规模陷阱
文章指出,原报告将Go侧默认1秒基准(约640万次迭代)与仓颉侧5000次操作的读数直接相除,得出“慢1.3倍”的结论。但Go的跳表基准中arena随迭代次数线性增长,同一二进制下仅改变规模就能让ns/op变化50%。对齐到相同N=5000后,实际差距约为2.6倍。这提醒读者,跨语言性能对比必须确保测量规模、线程数和预热方式一致,否则比值毫无意义。
FFI调用的固定开销
作者原以为FFI memcmp能加速长key比较,但实测显示其每次调用有约70纳秒固定开销,且耗时与key长度无关。在随机key基准中,逐字节比较因头几个字节就分出胜负而提前退出,仅需4纳秒,反而快约18倍。FFI仅在需要扫描长公共前缀时才可能划算,而随机key无法验证这一场景。因此,minLen<64的阈值仍是未经验证的启发式,输入分布决定了基准测的是什么。
io_uring的适用边界
重测确认io_uring在小文件低并发场景下慢2–6倍,比原报告的0.66x更差;但在大写入量、多imm表并发flush时,可快1.1–1.6倍。文章强调,结论必须带上适用条件,不能用一个场景的结果下普适判断。此外,所有倍数的离散度在±26%到±102%之间,只能用于判断方向,不宜引用具体数值。失败时系统会自动降级到同步写,这增加了鲁棒性。
Q&A
为什么作者要重跑 BadgerDB 仓颉移植版的基准测试?
作者需要为技术文章提供可靠数据,而原有的 benchmark-report.md 数据积累半年,若要用于技术选型判断,必须先验证其可复现性。重跑后发现一半数据经不起复现,且有三条结论完全错误、方向相反。
BadgerDB 仓颉移植版的并发写性能实际比 Go 慢多少?之前为什么误判为 1.3 倍?
实际慢约 2.6 倍。之前误判是因为拿两组不同规模的数字相除:Go 侧取自默认 -benchtime=1s(约 640 万次迭代),仓颉侧取自 5000 次操作,规模相差约 1300 倍。对齐到相同 N=5000 后,Go 为 149.9 ns/op,仓颉为 387 ns/op,比值约 2.6 倍。
在仓颉移植版中,FFI memcmp 和逐字节比较 key 哪个更快?为什么?
在随机 key 场景下,逐字节比较反而快约 18 倍(4 ns/op vs 72 ns/op)。因为 FFI 调用有约 70 纳秒的固定开销,且耗时与 key 长度无关;而随机 key 头几个字节就分出胜负,逐字节循环提前退出。FFI memcmp 只有在需要扫描大量字节(如共享长前缀)时才可能划算。
io_uring 在 BadgerDB 仓颉移植版中的性能表现如何?适用于哪些场景?
io_uring 的收益取决于 workload:小文件、低并发时慢 2–6 倍(提交/完成开销占主导);大写入量、多 imm 表并发 flush 时可快 1.1–1.6 倍。失败时会自动降级到同步写。
重跑基准后,作者总结出哪些方法学教训?
三个错误结论的共同点是方法错误而非测量错误:规模没对齐(跨语言对照时默认规模不同)、输入分布不匹配(随机 key 无法代表共享前缀负载)、结论超出证据边界(拿单一场景下普适结论)。这些错误会导致方向性错误结论,而非数值偏差。
为什么说“FFI memcmp 让 key 比较快 30%”这个结论是反的?
因为实测显示 FFI memcmp 比逐字节比较慢约 18 倍(72 ns/op vs 4 ns/op),且耗时与 key 长度无关,说明瓶颈是跨语言调用的固定开销(约 70 纳秒)。原结论基于错误假设,未考虑随机 key 下逐字节提前退出的情况。