为了探索和改进 AI 工具在编程方面的体验,同时也想验证一些 AI 的边界,于是又想到了尝试从 0 实现高性能JSON解析器,说干就干。 开始以为比较简单,不会超过半天就能实现,但是经过各种提示词优化,最终花了两天时间...
现在有各种 AI Copilot,比较常用的 Cursor,Windsurf,Trae 等,不过我现在用的比较顺手的是:Windsurf。 除了编程工具,然后就是模型,目前代码领域比较强的:
我在 Windsurf 上,使用 Claude 3.7 Sonnet 和 GPT-4.1 互相切换,简单问题 GPT-4.1 能快速解决,复杂的问题可以尝试 Claude 3.7 Sonnet 和 Thinking 分析,不过在使用过程中发现 Gemini 2.5 Pro 在分析性能上很强大,但是上下文不太够(可能是 Windsurf 上下文导致 prompt 太多了),对于代码超过 200 行的效果不是很好。
如果使用辅助编程工具,其实 Prompt 不是特别重要,对于开发者最重要的是如何把需求描述清楚。
比如本项目最开始的 Prompt 是:
用 golang 实现一个类似标准库 "encoding/json" 的 JSON 解析器,可以参考 github 的 cJSON
通过如上 Prompt,将会获得比较粗的代码实现,这个时候不应该基于是实现其他的功能,而是开始让 AI 帮你生成测试用例:
基于 @lexer.go 生成测试用例,其中测试用例需要覆盖如下 token 的支持:
EOFToken // 文件结束标记
NumberToken // 数字标记,例如:123, 45.67
StringToken // 字符串标记,例如:"hello"
NullToken // null值标记
TrueToken // true布尔值标记
FalseToken // false布尔值标记
CommaToken // 逗号标记 ','
ColonToken // 冒号标记 ':'
LeftBraceToken // 左大括号标记 '{'
RightBraceToken // 右大括号标记 '}'
LeftBracketToken // 左方括号标记 '['
RightBracketToken // 右方括号标记 ']'
为什么生成测试用例很重要? (1)验证代码的功能性问题,当你的代码覆盖率做的足够高,生成的代码就更安全。 (2)通过测试用例可以更好的提示 AI 生成代码,类似触发 CoT(Chain of Thought)。 (3)测试用例可以更好的帮助开发者理解独立的模块。
生成测试用例后,可能会遇到各种测试用例不通过的情况,Prompt 其实就将报错信息输入给 AI 即可:
@parser_test.go#L291-729 @types.go @parser.go 执行测试用例出现栈溢出的错误:runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0x14020260340 stack=[0x14020260000, 0x14040260000]
fatal error: stack overflow
runtime stack:
runtime.throw({0x10308321f?, 0xa2284a1184611846?})
/opt/homebrew/Cellar/go/1.24.1/libexec/src/runtime/panic.go:1101 +0x38 fp=0x16d4ced90 sp=0x16d4ced60 pc=0x102f9fff8
runtime.newstack()
/opt/homebrew/Cellar/go/1.24.1/libexec/src/runtime/stack.go:1107 +0x45c fp=0x16d4ceed0 sp=0x16d4ced90 pc=0x102f8682c
runtime.morestack()
/opt/homebrew/Cellar/go/1.24.1/libexec/src/runtime/asm_arm64.s:342 +0x70 fp=0x16d4ceed0 sp=0x16d4ceed0 pc=0x102fa57f0
...
需要注意: 测试用例一定要缩小范围,并且测试用例最好按照 标记 #%d 类型错误: 期望 %v, 得到 %v
(参考如下代码),让 AI 更好的理解问题出在哪里(期望 -> 得到
)。
比如本项目的测试用例都会将错误信息,原始信息等都打印出来:
for _, tt := range tests {
testCase := tt // 避免闭包问题
t.Run(testCase.name, func(t *testing.T) {
lexer := NewLexer(testCase.input)
for i, expected := range testCase.expected {
got := lexer.NextToken()
if got.Type != expected.Type {
t.Errorf("标记 #%d 类型错误: 期望 %v, 得到 %v", i, expected.Type, got.Type)
}
if got.Value != expected.Value {
t.Errorf("标记 #%d 值错误: 期望 %q, 得到 %q", i, expected.Value, got.Value)
}
if got.Pos != expected.Pos {
t.Errorf("标记 #%d 位置错误: 期望 %d, 得到 %d", i, expected.Pos, got.Pos)
}
}
})
}
从实践经验来看,随着功能的叠加,AI 生成的代码在单个文件会越来越长,但是这样会遇到一些问题(上下文限制,模型思考慢,问题分析不准确),因此需要定期将单个文件按照功能拆分多个文件,建议单个文件不超过 200 行(测试用例倒是不需要,由于测试用例是单一功能的),在 sjson
的代码行数基本上都少于 200 行:
提出一个问题 让 AI 解决,可能方案有很多,比如 JSON 解析可以用方案:
但是 AI 一开始并不一定能给出最优的方案,比如本项目开始提供分阶段解析方案,但是参考其他的开源项目,都是用流式解析,该方案对于 JSON 解析器比较合适(因为没有需要动态计算的过程,所以扫一遍就可以处理,性能要比分段解析好),当然也要考虑业务场景,比如要实现动态脚本或者 expr
功能,分阶段解析更合适,所以在实现之前可以先了解当前领域的知识并分析方案的优劣势(其实整个分析的过程也可以喂给 AI 来做判断并纠正),然后让 AI 按照提示的方向实现。
不过值得注意的是,当提出用 JIT 等方案优化,AI 会提示方案不合理(实现的确不合理,JIT 需要增加各种适配代码)。
sjson
需要对比其他库的功能是否完整,依赖 AI 通常不一定能给全所有的测试用例,所以需要找到测试套件,这里我参考开源项目 https://github.com/nst/JSONTestSuite
,修改 run_tests.py
:
programs = {
"JavaScript":
{
"url":"",
"commands":["node", os.path.join(PARSERS_DIR, "test_json.js")]
},
"Python 2.7.10":
{
"url":"",
"commands":["python", os.path.join(PARSERS_DIR, "test_json.py")]
},
"sjson":
{
"url":"",
"commands":["/Volumes/my/github/mylib/go/sjson/tests/example", "-a"]
},
"stdjson":
{
"url":"",
"commands":["/Volumes/my/github/mylib/go/sjson/tests/example", "-b"]
},
"jsoniterator":
{
"url":"",
"commands":["/Volumes/my/github/mylib/go/sjson/tests/example", "-c"]
},
}
可以跑通 300+
个测试用例,其中与标准库 encoding/json
和 Jsoniter
的对比如下:
通过 AI 生成 Benchmark
函数,然后进行性能的测试:
BenchmarkComplexJSON/Original-14 13598248 5525 ns/op 9993 B/op 178 allocs/op
BenchmarkComplexJSON/Optimized-14 12703338 5804 ns/op 10645 B/op 179 allocs/op
BenchmarkComplexJSON/Standard-14 17148706 4125 ns/op 5136 B/op 107 allocs/op
以上是第一轮的性能测试,比标准库性能差了 60%,然后可以将当前性能测试数据当成 Prompt 输入:
@sjson_marshal.go 性能测试如下:
BenchmarkComplexJSON/Original-14 13598248 5525 ns/op 9993 B/op 178 allocs/op
BenchmarkComplexJSON/Optimized-14 12703338 5804 ns/op 10645 B/op 179 allocs/op
BenchmarkComplexJSON/Standard-14 17148706 4125 ns/op 5136 B/op 107 allocs/op
基于当前性能对比进行性能优化,比如增加缓存,减少 decode 次数,减少 fmt.Sprintf 改为 byte[] 和 append 或者 strings.Builder,目标是减少内存分配和数据拷贝次数。
... ...
然后经过一系列漫长的优化,最后性能对比标准库:
BenchmarkUnmarshalCompareMedium/SjsonUnmarshal-14 2203020 5576 ns/op 5923 B/op 115 allocs/op
BenchmarkUnmarshalCompareMedium/StdUnmarshal-14 1484354 8036 ns/op 504 B/op 11 allocs/op
BenchmarkUnmarshalCompareMedium/JsoniterUnmarshal-14 5908438 1986 ns/op 352 B/op 38 allocs/op
性能已经比 encoding/json
标准库提升 80%
,虽然比 Jsoniter
性能差(Jsoniter开启了 ConfigFastest),但是可以尝试引入 reflect2
等方案来提升性能,从 flamegraph
看需要对反射和拷贝的方向进行优化:
(1)代码已经开源:https://github.com/linkxzhou/mylib/tree/master/go/sjson
(2)继续性能优化,尝试探索让 AI 如何对项目进行性能优化和构建 go pprof MCP Server
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有