改一处要花多少:寻址成本基准测试
一句话结论:说清楚"改哪里",Markdown 要花 21 倍。
同一批文档、同一批 47 处修改。Markdown 里指认一个位置,只能把原文引一遍—— 13,109 字节;GEML 里只要一个地址——611 字节。新增的内容两边完全相同, 已从这个数字里排除,所以剩下的 21.45× 全部是格式造成的差异。
这不是引用一次性测量。跑一条命令就能复现:
GEML_SRC=../geml node benchmarks/addressing-cost.mjs为什么测这个
一份文档写完之后,绝大多数时间花在改它,而每一次修改都分两步:找到那一处, 然后说清楚要改的就是那一处。人用眼睛和滚动条完成第一步,成本不进账; 但当改文档的是程序——编辑器插件、CI 脚本、大模型 agent——两步都要花钱: 读进来的内容是钱,写出去的内容也是钱,而且写出去的更贵。
Markdown 没有稳定的部件名。要指认一段话,只能引用它的原文,而且引到足够长、 在全文唯一为止。GEML 里每个块有地址,指认它就是写下那个地址。
这份基准测试量的就是这个差别。
怎么测的
设计在第一次运行之前就已固定,脚本开头的注释是原文。
| 语料 | 本仓库里同时存在两种格式的 4 份文档:规范与 history 规范,中英各一,16.8 KB–70.6 KB。同样的内容、同样的标题数量。A 组改 Markdown,B 组改 GEML,谁也没拿到更好写的那份 |
| 取样 | 机械抽样:每第 N 个可寻址块,每份文档约 12 个,不做人工挑选。唯一排除的是文档级 H1——它的"块"就是整个文件,没有人把一整份文档当作一次修改 |
| 任务 | "替换块 B 的内容"。修改者知道要改什么,不知道它在哪,所以两组都必须先找。搜索短语由一条规则生成——块的第一行 ≥12 字符的内容——两组搜同一个短语 |
| A 组(Markdown) | grep -n 定位 → sed -n '命中,+45p' 读一个 46 行窗口 →(块装不下时再读一次)→ 写出一段唯一的 old_string,再写新内容 |
| B 组(GEML) | geml find 定位并拿到地址 → geml get <地址> 精确读那一块 → 写出地址,再写新内容 |
新增内容两组完全一样,所以不计入"说清楚改哪里"那一项——剩下的就是每种格式 为"指认位置"收的费。
有两处刻意让 A 组占便宜,好让结果是下限而不是漂亮案例:
- 窗口取 46 行,是实测中一个 agent 在真实工作中所用窗口的中位数, 而不是一个保险的大窗口;
old_string按最短唯一前缀计算——即仍然能正确命中的最省写法。
结果
47 处修改,4 份文档:
| Markdown | GEML | 比值 | |
|---|---|---|---|
| 说清楚改哪里(写出去的字节) | 13,109 | 611 | 21.45× |
| 读进来的字节 | 107,861 | 44,155 | 2.44× |
| 每处修改读进来的中位数 | 2,124 | 565 | 3.76× |
| 往返次数 | 103 | 94 | 1.10× |
- GEML 更贵的修改:0 / 47。 没有一处例外。
- 46 行窗口装不下目标块:9 / 47(19%)。 这时 Markdown 一侧必须再读一次—— 而窗口开多大本来就是猜的,猜小了漏内容,猜大了白读。
- 逐处修改的读取比值:最小 1.41× · 中位 3.10× · 最大 18.12×。
四份文档各自独立计算,结果一致,不是被某一份带偏的:
| 文档 | n | 读取 | 说清楚改哪里 |
|---|---|---|---|
| GEML-spec_CN.md | 12 | 2.13× | 10.7× |
| GEML-spec.md | 11 | 2.91× | 11.0× |
| GEML-history-spec_CN.md | 12 | 2.41× | 25.6× |
| GEML-history-spec.md | 12 | 2.30× | 32.7× |
一个旁证
A 组每处修改读进来的中位数是 2,124 字节。另一次完全独立的测量——把一个 agent 一整天真实编辑文档的会话记录逐条统计出来——得到 2,133 字节。 不同的语料、不同的方法,落在 0.4% 以内。这说明上面这套流程复现的是真实行为, 而不是为了好看而设计的模型。
这个测试没有证明什么
- 往返次数几乎没省(1.10×)。
find+get是两次调用,grep+sed也是两次。 GEML 省的是每次调用搬多少东西,不是调用次数。 - 块越小,差距越大。这批文档是规范,块偏大;在块更小的文档上读取比值会更高, 在少数极大的块上会更低。中位 3.10× 比总量 2.44× 更能代表"典型的一次修改"。
- 这不衡量写作本身。要把一段话写好,需要读懂上下文——这部分成本两种格式一样。 这里量的只是找到它、指认它。
- 只测了 4 份文档、47 处修改,全部来自本仓库。语料越多结论越稳,脚本可以直接 换语料重跑。
为什么差距在"说清楚改哪里"这一项上最大
因为这一项两种格式的做法根本不同。
Markdown 里没有别的办法:要让一次替换命中正确的位置,就得把原文引够——短了会 撞上文档里另一处相同的文字,改错地方。文档越长、重复的措辞越多,要引的就越长。
GEML 里这一步是一个地址:#3-块 或 === table@412f8f61。地址不随内容变长, 也不随文档变长。
而且这一项花的是写出去的字节——对按 token 计费的系统来说,写比读贵。
复现
git clone https://github.com/geml-spec/geml && cd geml
cd geml-parser && npm install && npm run build && cd ..
GEML_SRC=../geml node benchmarks/addressing-cost.mjs
GEML_SRC=../geml node benchmarks/addressing-cost.mjs --json > result.json # 逐条数据本文数字对应 geml-spec 0c6be2c。基准两侧都真跑,语料就是仓库里那四份文档—— 所以改动 spec 或 CLI 之后重跑,末位会变(107,889 → 107,861 就是一次 spec 修订 带来的 28 字节)。数量级和结论不受影响,要引用精确值请自己跑一遍。
脚本、语料、抽样规则都在仓库里。要换成你自己的文档,改脚本顶部的 PAIRS—— 需要同一份内容的 Markdown 与 GEML 两个版本,geml <file.md> --from md --to geml 可以生成后者。