大家都在说 Go 不适合做底层基础设施,都在说 C 语言的地位不可撼动。
直到有人把 Tree-sitter——这个被誉为"解析器界瑞士军刀"的 C 语言项目——完整移植到了 Go。
结果基准测试跑出来,所有人都沉默了:全量解析快 1.5 倍,增量解析快 90 倍,而在无编辑重解析的场景下,纯 Go 版本甚至比原版 C 快了 14000 倍。这哪里是移植?这简直是把原版按在地上摩擦。
"C 一定比 Go 快"的神话破灭了
说实话,看到这个数据的第一眼,我也以为是测试人员把小数点点错了。
但事实就是如此荒诞。在 BenchmarkGoParseIncrementalNoEdit 测试中,纯 Go 版本耗时 8.63 纳秒,而通过 CGo 调用原版 C 库耗时 121,100 纳秒。
差了四个数量级。
为什么会这样?难道 Go 的代码写得比 C 还好?其实原因很朴素,也很残酷:CGo 的调用开销太大了。
原版 Tree-sitter 是 C 写的,Go 要用它就得通过 CGo 绑定。每次调用都得穿越 Go 运行时和 C 运行时的边界,这一来一回,时间全花在了"过路费"上。
而纯 Go 版本呢?
增量解析的热路径激进地重用子树,单字节编辑只需微秒级。无编辑的快速路径更绝,直接在一个 nil 检查上退出——零内存分配,个位数纳秒。
没有边界,没有开销,只有纯粹的逻辑。
摆脱 CGo,是多少开发者的梦想
老实讲,CGo 一直是 Go 开发者心中的痛。
你想用一个强大的 C 库,就得接受 CGo 带来的复杂性:编译环境难搞、交叉编译困难、部署体积膨胀、性能损耗……
这就像你想吃顿好的,结果发现得先自己种菜、养猪、搭灶台。
这也是为什么 gotreesitter 这个项目一出,评论区立马炸了锅。
有开发者兴奋地表示,这对 Bazel 社区简直是天大的好消息。
因为 Gazelle 语言扩展是用 Go 写的,以前想依赖 Tree-sitter 就得用 CGo,现在终于可以摆脱这个"诅咒"了。
还有人提到,对于像 Gitea、Forgejo 这种基于 Go 的代码托管平台,这东西简直是语法高亮的神器。
"我有严格的 no-cgo 要求,所以可能会在我的项目里用这个。"
你看,有些时候,"纯 Go"不仅仅是一种技术选型,更是一种信仰。
205 种语法,一场硬核的"搬运战"
移植 Tree-sitter 可不是简单的翻译工作。
Tree-sitter 最强大的地方在于它支持海量的编程语言语法。要移植它,就意味着要把这些语法的解析逻辑全部搬过来。205 种语法。
从 Ada 到 Zig,从 C 到 Rust,甚至包括那些你听都没听过的语言,比如 Beancount、Cooklang、Norg。
作者不仅要移植核心解析器,还要处理大量的词法分析器。其中 195 个使用了 DFA(确定性有限状态自动机)后端,还有 9 个需要手写纯 Go 的词法分析器桥接。
最麻烦的是外部扫描器。
有些语言的语法特别刁钻,通用的解析器搞不定,就得手写代码去处理。作者硬是为 111 种语言手写了 Go 版本的外部扫描器。这工作量,想想都让人头皮发麻。
并不完美,但足够惊艳
当然,这世界上没有完美的项目。
在评论区里,除了叫好声,也夹杂着一些冷静的质疑。
有人指出,这个移植版缺少 TreeCursors 和 tree-sitter-generate。
TreeCursors 是一种高效的树遍历 API,没有它,有些场景下查询和遍历都不太顺手。而 tree-sitter-generate 更是关键,它能让你动态生成和编译语法,这对于某些需要运行时扩展的用户来说,是刚需。还有人注意到了 "partial" 模式的问题。
目前有一个语言——norg——是部分支持的,因为它的外部扫描器还没实现。这导致在某些语法下,解析结果可能不正确。
"为什么你会有一种保证在某些语法下输出错误的解析模式?"
这个问题挺尖锐。
不过,考虑到这是一次"从零开始"的移植,支持了 205 种语言中的 204 种完整解析,这成绩单已经足够傲人了。
名字是个大问题
最后,聊聊那个有点尴尬的小插曲。有人建议项目名叫 got。
结果立马有人跳出来反对:这不就跟 OpenBSD 的 Got(Game of Trees)撞车了吗?
在开源世界里,起名是个技术活。叫 gotreesitter 虽然有点长,但至少安全,不会引起混淆。
有意思的是,还有人提到之前也有人用 Rust 重写过 Tree-sitter,但似乎对最终用户来说,用处没那么大。
技术移植这事儿,光有技术还不够,还得找准生态位的痛点。Go 生态对 CGo 的"嫌弃",恰恰给了 gotreesitter 最大的生存空间。
结语
14000 倍的性能提升,或许只是极端场景下的个例。
但它揭示了一个趋势:随着语言 runtime 的进化,"C 一定快"的刻板印象正在瓦解。
当 Go 摆脱了 CGo 的枷锁,它所爆发出的能量,足以让任何一个 C 语言老手侧目。
不过,我也想问一句:如果未来 WASM 成了主流,这种"原生移植"的故事,还会继续上演吗?
参考链接:
https://github.com/odvcencio/gotreesitter