CodeGraph A/B 基准实测:37 个语言 × 规模单元格,量化 codegraph 带来的 76% 文件读取削减
本文解读 CodeGraph 仓库中 2026-05-24 完成的 with/without A/B 基准矩阵(记录于 docs/benchmarks/codegraph-ab-matrix.md):同一个 headless agent 对 37 个不同语言、不同规模的真实仓库各回答一道"canonical flow"问题,唯一变量是有没有接入 codegraph MCP 服务。读完本文,你将掌握这套 A/B 协议如何做到单变量控制与防污染、每个指标(R/G/Gl/B/Ag、cg-calls、reads saved)的精确读法,以及如何用仓库自带 harness 在自己的仓库上复现同样的对照实验。
基准设计:codegraph 是唯一变量
该实验的核心约束是:同一模型、同一 prompt,唯一变量是 codegraph MCP 服务。具体设置(见原文档头部,由 scripts/agent-eval/run-all.sh 实现):
- 被测 agent 是 headless 模式的 Claude Opus,
--permission-mode bypassPermissions,每道题跑两个 arm:with(接入 codegraph MCP)与 without(空 MCP,仅内置 Read/Grep/Glob/Bash); - 每个单元格先
rm -rf .codegraph && codegraph init -i重新全量索引(针对当前mainHEAD 的dist/构建),确保 with-arm 反映的是 codegraph 0.9.4 已发布的解析器,而不是过期索引; - 每个仓库只问一道 canonical flow 问题(例如"请求如何从路由到达 handler、再到达 service"),两个 arm 问的是同一道题;
- 日期 2026-05-24,分支
main,版本 codegraph 0.9.4。
从 run-all.sh 的源码可以确认几个关键实现细节,它们保证了"唯一变量"承诺的可信度:
- with-arm 写入的 MCP 配置只有 codegraph 一个服务:
{"command":"$CG_BIN","args":["serve","--mcp","--path","$REPO"]};without-arm 写入的是{"mcpServers":{}},两个 arm 都用--strict-mcp-config启动claude -p,内置 Read/Grep/Bash 在两边都可用——所以对比的是"图检索"与"文件检索",而不是"有工具"对"没工具"; - 脚本显式
export CODEGRAPH_NO_PROMPT_HOOK=1,注释写明原因:环境中的 CodeGraph prompt-hook 会向每个 prompt 注入上下文,会污染 without-arm(白得结构上下文)、同时重复计入 with-arm; - 更隐蔽的漏洞是 CLI 泄漏:without-arm 虽然没有 MCP,但它有 Bash,而目标仓库里就放着
.codegraph/索引、PATH上就有codegraph二进制。据 scripts/agent-eval/no-cli-shim.sh 头部注释,一次 7 仓库遍历中 14/15 的 without-arm 跑法都通过 Bash 调用了codegraph explore——那测的其实是"codegraph-over-CLI",不是"没有 codegraph"。因此 harness 用两层封锁:一层是替换 PATH 目录(对 codegraph 所在目录做符号链接镜像、唯独去掉 codegraph,保持 PATH 顺序与优先级),另一层是 PreToolUse hook 拒绝任何命令位置的 CLI 调用(因为曾有 agent 被拒后用find /找到二进制并以绝对路径执行)。README 中 2026-08 的复测也记载了未封锁 harness 的污染率:28 次 without 跑法中 26 次通过 Bash 摸到了 CLI。
关键结论(Headline)
37 个单元格合计,codegraph 把总文件读取从 159 次降到 38 次——减少 76%,且没有任何一个单元格里读取数反而变多(0 次回归)。 机制是:几次亚毫秒级的 codegraph 调用替代了"读取+grep"式的探索。
成本大致持平,且在本实验中 with-arm 略高(37 单元格求和:with $15.4 vs without $13.8)。原文档对此的解释值得注意:在这些短的单流问题上,without-arm 通常 10 次调用内就能解决、从不膨胀,还没到 codegraph 成本节省能复利累积的区间;而 with-arm 要支付固定的 MCP 开销(工具定义常驻上下文 + 工具加载),短任务摊不薄。真正的收益是 工具调用更少(189 vs 321,−41%)+ 墙钟时间更短(均值 38s vs 48s)——这才是设计目标。在更难的多轮调查中,随着 without-arm 累积的上下文膨胀,成本会翻转为净节省(见 docs/benchmarks/call-sequence-analysis.md)。
差距随仓库规模与流程复杂度扩大:在 M/L 仓库上,without-arm 经常陷入 thrash——大量 grep/glob、shell find/grep(Bash),偶尔还派生 sub-agent;而 with-arm 用 2–8 次调用就回答完毕。在极小仓库(几个文件)上,两 arm 打平甚至 codegraph 略慢(MCP/索引开销在"整个流程只占一两个文件"时不划算)——但读取数依然下降。
如何阅读结果矩阵
原文档给出了一份阅读约定,复现时对照表格必须遵守:
- R / G / Gl / B / Ag = Read / Grep / Glob / Bash / sub-agent(Task)工具调用次数;
- cg-calls = with-arm 中的 codegraph MCP 调用次数(它换掉的是 reads/greps);
- dur = 墙钟秒数;files = 被索引文件数(规模代理指标);
- reads saved = without-reads − with-reads;
- 每个 arm 只跑一次(snapshot):逐次波动是真实存在的,±1–2 次读取、±10s 应视为噪声,看跨单元格的模式;其中部分流程的 2-runs/arm 数字记录在 docs/design/dynamic-dispatch-coverage-playbook.md §7。
完整结果矩阵(37 单元格)
以下表格完整继承自原文档:
| Language | Size | Repo | files | with R/G | cg-calls | dur | without R/G | dur | reads saved |
|---|---|---|---|---|---|---|---|---|---|
| C | L | c-redis |
884 | 0R / 2G | 4 | 42s | 5R / 6G | 51s | 5 |
| C# | S | aspnet-realworld |
78 | 0R / 0G | 2 | 27s | 5R / 3G / 2Gl | 54s | 5 |
| C# | M | aspnet-eshop |
262 | 0R / 1G | 5 | 39s | 9R / 2G / 5Gl | 58s | 9 |
| C# | L | aspnet-jellyfin |
2081 | 3R / 0G | 4 | 51s | 17R / 1G / 2Gl / 17B / 1Ag | 212s | 14 |
| C++ | M | cpp-leveldb |
134 | 0R / 0G | 3 | 26s | 4R / 2G | 37s | 4 |
| Dart | S | flutter_module_books |
6 | 1R / 0G | 2 | 24s | 2R / 0G / 1Gl | 29s | 1 |
| Dart | M | compass_app |
212 | 2R / 0G / 1Gl | 2 | 42s | 3R / 0G / 2Gl | 30s | 1 |
| Go | S | gin-realworld |
21 | 0R / 0G | 5 | 35s | 4R / 3G / 1Gl | 57s | 4 |
| Go | M | gin-vueadmin |
625 | 1R / 1G | 4 | 47s | 3R / 3G / 1Gl | 44s | 2 |
| Go | L | gin-gitness |
4438 | 4R / 3G | 4 | 64s | 8R / 7G / 2Gl | 57s | 4 |
| Java | S | spring-realworld |
117 | 2R / 0G | 3 | 35s | 8R / 1G / 5B | 57s | 6 |
| Java | M | spring-mall |
536 | 1R / 0G | 5 | 39s | 2R / 4G / 2Gl | 49s | 1 |
| Java | L | spring-halo |
2444 | 1R / 2G | 8 | 60s | 4R / 1G / 6B | 52s | 3 |
| Kotlin | S | kotlin-petclinic |
43 | 0R / 0G | 2 | 37s | 3R / 0G / 1Gl | 23s | 3 |
| Kotlin | M | Jetcaster |
166 | 1R / 0G | 3 | 36s | 1R / 0G / 2Gl | 46s | 0 |
| Lua | S | lualine.nvim |
123 | 1R / 1G | 4 | 48s | 4R / 0G / 2Gl | 49s | 3 |
| Lua | M | telescope.nvim |
84 | 0R / 0G | 1 | 15s | 1R / 0G / 1Gl | 20s | 1 |
| Luau | S | Knit |
11 | 0R / 0G | 2 | 30s | 5R / 0G / 2Gl | 37s | 5 |
| PHP | S | laravel-realworld |
114 | 1R / 0G | 6 | 40s | 5R / 1G / 3Gl | 39s | 4 |
| PHP | M | laravel-firefly |
2047 | 2R / 1G | 4 | 47s | 4R / 5G / 3Gl | 75s | 2 |
| PHP | L | laravel-bookstack |
2160 | 1R / 2G | 2 | 41s | 2R / 4G / 1Gl | 50s | 1 |
| Python | S | django-realworld |
44 | 2R / 1G | 2 | 47s | 9R / 0G / 1B | 38s | 7 |
| Python | M | django-wagtail |
1672 | 2R / 0G | 4 | 45s | 8R / 3G / 3Gl / 1B | 66s | 6 |
| Python | L | django-saleor |
4429 | 2R / 2G | 4 | 52s | 4R / 6G / 1Gl | 64s | 2 |
| Ruby | S | rails-realworld |
59 | 0R / 0G | 2 | 30s | 3R / 0G / 2B | 33s | 3 |
| Ruby | M | rails-spree |
2905 | 2R / 3G / 1Gl | 5 | 43s | 3R / 3G / 2Gl / 1B | 55s | 1 |
| Ruby | L | rails-forem |
4658 | 3R / 1G | 3 | 43s | 4R / 2G / 3Gl | 48s | 1 |
| Rust | S | rust-axum-realworld |
13 | 0R / 0G | 2 | 21s | 3R / 0G / 1Gl | 38s | 3 |
| Rust | M | rust-actix-examples |
176 | 0R / 1G | 3 | 42s | 3R / 0G / 3B | 36s | 3 |
| Rust | L | rust-cratesio |
1053 | 1R / 0G | 3 | 22s | 1R / 2G | 18s | 0 |
| Scala | S | computer-database |
10 | 1R / 0G | 2 | 27s | 3R / 0G / 1Gl | 25s | 2 |
| Swift | S | vapor-template |
14 | 0R / 0G | 2 | 21s | 2R / 0G / 2Gl | 22s | 2 |
| Swift | M | vapor-steampress |
100 | 0R / 0G | 5 | 49s | 3R / 1G / 2Gl | 39s | 3 |
| Swift | L | vapor-spi |
542 | 1R / 1G | 4 | 27s | 2R / 5G | 34s | 1 |
| TypeScript/JS | S | express-realworld |
39 | 1R / 0G | 1 | 25s | 2R / 2G | 19s | 1 |
| TypeScript/JS | M | excalidraw |
643 | 1R / 0G | 3 | 55s | 7R / 5G / 3Gl / 1B | 87s | 6 |
| TypeScript/JS | L | nest-immich |
2759 | 1R / 0G | 7 | 50s | 3R / 0G / 1Gl | 44s | 2 |
37 单元格总计: with codegraph 38 reads / 22 greps,without 159 reads / 72 greps——读取 −76%,grep −约69%。codegraph 没有任何一个单元格让读取变多;without-arm 额外跑了 52 次 glob + 37 次 shell find/grep(Bash)+ 1 个 sub-agent,而 with-arm 0 Bash、0 sub-agent(74 次 agent 运行,总花费 $29.18)。
观察:收益模式与平局区
原文档的五条观察,归纳出 codegraph 收益的边界条件:
- 最大收益出现在 M/L 级、有真实 route→handler→service 流程的后端:
aspnet-jellyfin(3R / 51s vs 17R + 17 Bash + 1 个派生 sub-agent / 212s,全矩阵最戏剧化的一格)、aspnet-eshop(0R vs 9R)、django-realworld(2R vs 9R)、spring-realworld(2R vs 8R + 5 Bash)、django-wagtail(2R vs 8R)、excalidraw(1R / 55s vs 7R / 87s)、LuauKnit(0R vs 5R)、aspnet-realworld(0R vs 5R)、c-redis(0R vs 5R)。 - 大仓库 + 无 codegraph = thrash:agent 回退到 shell
find/grep(全矩阵共 37 次 Bash),jellyfin 甚至派生了 sub-agent——这正是 codegraph 要消灭的行为。with-arm 对这些问题用 2–8 次 codegraph 调用回答,且全局 0 Bash、0 sub-agent。 - 平局区 = 极小仓库(Jetcaster 1R/1R、cratesio 1R/1R、express 1R/2R、vapor-template 0R/2R):整个流程只占 1–2 个文件,读取本来就便宜;codegraph 在读取上打平,有时还慢几秒(MCP + 索引开销——Kotlin petclinic 37s vs 23s、cratesio 22s vs 18s)。这与"codegraph 的价值随仓库规模增长"的设计判断一致。
- 大仓库上墙钟时间跟随读取数(jellyfin 51s vs 212s、excalidraw 55s vs 87s、aspnet-eshop 39s vs 58s、django-wagtail 45s vs 66s),小仓库上则是噪声;均值墙钟 with 38s vs without 48s。
- 部分 with 单元格仍读 2–4 个文件(jellyfin、gitness、forem、saleor、django)——原文档把这定义为"已记录的 frontier"(匿名 handler、深层 service 链、动态查找器);codegraph 把 agent 送到正确的文件,它再读一个文件确认细节。
覆盖说明
README 列出的全部 14 个框架与所有流程相关语言均已验证(见 playbook)。规模按索引文件数划分;语料中部分语言没有干净的第三档规模(Dart/Kotlin 只有 S/M,Scala/Luau 只有 S,C 只有 L,C++ 只有 M)——这些单元格被省略而不是伪造。
harness 如何统计:parse-run.mjs 的度量口径
矩阵里每个数字不是人工数的,而是从 claude -p 的 stream-json 日志解析出来的。scripts/agent-eval/parse-run.mjs 的 parseSession 把一次会话(多轮会话的多个 segment 文件按序拼接)统计为:
- 工具家族计数:
codegraph/read(Read/NotebookRead)/search(Grep/Glob)/bash(Bash/BashOutput)/other,表格里的 R/G/Gl/B/Ag 即来自counts.Read、Grep、Glob、Bash与Task调用;grep列是 Grep + Glob 之和,dur与cost分别累加各 segment 的duration_ms与total_cost_usd; - CLI 污染计数器:日志里任何命令位置的
codegraph调用(注意区分"调用"与"提及"——grep codegraph src/、ls .codegraph、which codegraph都放行)会被记为一次 attempt;只有真正返回了输出的尝试才被计为 contaminated,因为被 hook 拒绝的尝试没有任何输出进入上下文窗口。这层"计数器"是 PATH 替换与 PreToolUse hook 之外的第三道防线——防堵措施会静默失败,计数器不会; - 除此之外,该解析器还输出三项反馈指标:residual context occupancy(会话结束时各工具家族仍占据的上下文窗口)、explore sufficiency(explore 调用之后 agent 的下一个动作)、allocation efficiency(答案实际引用的字节占 explore 返回字节的比例)。前两项指标的定义见 docs/benchmarks/agent-eval-feedback-metrics.md 与 run-all.sh 头部注释。
另外值得注意的一个测量陷阱(记录在 docs/benchmarks/call-sequence-analysis.md 中,2026-08-05 已修入 harness):当前版本 Claude Code 的 result.usage 只报告最后一轮而非累计值,直接读取会系统性低估轮数更多的那一 arm——而那永远是 without-arm。正确做法是按 assistant 轮逐次累加 usage(并按 message.id 去重,因为每个 content block 都会带同一份 usage 的事件)。这一 bug 曾把真实的 62% token 节省测成 19%,甚至在两个仓库上虚构出 token 回归。
复现方法
原文档给出的复现路径:
# 标准 harness:with = codegraph-only MCP,without = 空 MCP,指标从 stream-json 日志解析
scripts/agent-eval/run-all.sh <repo> "<question>" headless
参数与约定(结合 run-all.sh 源码补充):
- 第一个参数是已完成索引的仓库路径(脚本会检查
.codegraph/存在,否则要求先索引);第二个参数是问题,多轮问题用||分隔,后续轮次通过--resume接续同一会话; - 环境变量:
CG_BIN(codegraph 二进制,默认取PATH上的codegraph)、AGENT_EVAL_OUT(输出目录,默认/tmp/agent-eval)、MODEL/EFFORT(该 A/B 的常设政策是 sonnet / high,run-all.sh 注释明确"不要调高");CG_ARMS=with|without|both可只补跑其中一个 arm; - 该矩阵本身的一次性驱动脚本(
lang|size|repo|question矩阵,每格先rm -rf .codegraph && codegraph init -i再跑两个 arm)当时放在仓库外的/tmp/ab-matrix/:run.sh(矩阵驱动)、parse-matrix.mjs(cells → 上表)、compare.mjs(新旧对比 + 汇总)——注意这些文件不在仓库内,仓库内可复用的是run-all.sh+parse-run.mjs这条链路; - 前提:先从目标 commit 构建
dist/,让 MCP 服务加载的是被测代码(PATH上的codegraph通过npm link指向开发dist/)。
后续证据与边界
这份 2026-05 矩阵之后,仓库里有两笔直接相关的后续记录,可以帮你理解结论的演化:
- 为什么读得少不等于等比例快:docs/benchmarks/call-sequence-analysis.md 对同一批 37 单元格日志做了再挖掘(未重跑):总轮次 with 283 vs without 375(−25%),但墙钟只快约 16%——差额在于 with-arm 每轮携带约 18K 的 explore 载荷,抬高了每轮延迟。该文档后续还通过 ablation(arm A–I)验证了 trace/explore/context 三工具的取舍,是理解本矩阵中 cg-calls 列背后工具面演进的一手材料;
- 同一协议的后续复测:README 的 Benchmark Results 一节(2026-08-05,Claude Opus 4.8,7 个仓库 × 每 arm 4 次取中位数)在当前构建上复现了"35% cost / 59% tokens / 49% time / 70% tool calls"量级的收益,并确认了双 arm 封锁 CLI 后 0/28 污染;其口径差异(4 次取中位数 vs 本矩阵的单次 snapshot、7 个精选仓库 vs 37 格语言 × 规模矩阵)恰好说明为什么两者数字不同但不矛盾。
适用前提与限制:本文数字来自 2026-05-24 的单次 snapshot 运行(codegraph 0.9.4),单格波动可达 ±1–2 次读取与 ±10s,解读时应看跨格模式而非单格绝对值;成本结论("大致持平、with 略高")是针对短单流问题——原文档明确指出多轮场景下成本翻转为净节省,两个场景不应互相套用对方的成本结论。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00