前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用 Windsurf 从0实现高性能JSON解析器

用 Windsurf 从0实现高性能JSON解析器

作者头像
用户1904552
发布于 2025-05-09 04:35:51
发布于 2025-05-09 04:35:51
7200
代码可运行
举报
文章被收录于专栏:周末程序猿周末程序猿
运行总次数:0
代码可运行

为了探索和改进 AI 工具在编程方面的体验,同时也想验证一些 AI 的边界,于是又想到了尝试从 0 实现高性能JSON解析器,说干就干。 开始以为比较简单,不会超过半天就能实现,但是经过各种提示词优化,最终花了两天时间...

1. 选用工具

现在有各种 AI Copilot,比较常用的 Cursor,Windsurf,Trae 等,不过我现在用的比较顺手的是:Windsurf。 除了编程工具,然后就是模型,目前代码领域比较强的:

  • Claude 3.7 Sonnet 和 Claude 3.7 Sonnet Thinking
  • GPT-4.1
  • o4-mini-high
  • Gemini 2.5 Pro

我在 Windsurf 上,使用 Claude 3.7 Sonnet 和 GPT-4.1 互相切换,简单问题 GPT-4.1 能快速解决,复杂的问题可以尝试 Claude 3.7 Sonnet 和 Thinking 分析,不过在使用过程中发现 Gemini 2.5 Pro 在分析性能上很强大,但是上下文不太够(可能是 Windsurf 上下文导致 prompt 太多了),对于代码超过 200 行的效果不是很好。

2. Prompt

如果使用辅助编程工具,其实 Prompt 不是特别重要,对于开发者最重要的是如何把需求描述清楚。

比如本项目最开始的 Prompt 是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
用 golang 实现一个类似标准库 "encoding/json"JSON 解析器,可以参考 github 的 cJSON

通过如上 Prompt,将会获得比较粗的代码实现,这个时候不应该基于是实现其他的功能,而是开始让 AI 帮你生成测试用例:

代码语言:javascript
代码运行次数:0
运行
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 即可:

代码语言:javascript
代码运行次数:0
运行
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 更好的理解问题出在哪里(期望 -> 得到)。

比如本项目的测试用例都会将错误信息,原始信息等都打印出来:

代码语言:javascript
代码运行次数:0
运行
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)
            }
        }
    })
}

3. 限制文件大小

从实践经验来看,随着功能的叠加,AI 生成的代码在单个文件会越来越长,但是这样会遇到一些问题(上下文限制,模型思考慢,问题分析不准确),因此需要定期将单个文件按照功能拆分多个文件,建议单个文件不超过 200 行(测试用例倒是不需要,由于测试用例是单一功能的),在 sjson 的代码行数基本上都少于 200 行:

4. 提供方向性的指引

提出一个问题 让 AI 解决,可能方案有很多,比如 JSON 解析可以用方案:

  • 递归下降方法,边解析边赋值(流式解析)
  • 分阶段解析(词法+语法分析)

但是 AI 一开始并不一定能给出最优的方案,比如本项目开始提供分阶段解析方案,但是参考其他的开源项目,都是用流式解析,该方案对于 JSON 解析器比较合适(因为没有需要动态计算的过程,所以扫一遍就可以处理,性能要比分段解析好),当然也要考虑业务场景,比如要实现动态脚本或者 expr 功能,分阶段解析更合适,所以在实现之前可以先了解当前领域的知识并分析方案的优劣势(其实整个分析的过程也可以喂给 AI 来做判断并纠正),然后让 AI 按照提示的方向实现。

不过值得注意的是,当提出用 JIT 等方案优化,AI 会提示方案不合理(实现的确不合理,JIT 需要增加各种适配代码)。

5. 充分的测试

sjson 需要对比其他库的功能是否完整,依赖 AI 通常不一定能给全所有的测试用例,所以需要找到测试套件,这里我参考开源项目 https://github.com/nst/JSONTestSuite,修改 run_tests.py

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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/jsonJsoniter 的对比如下:

6. 性能优化

通过 AI 生成 Benchmark 函数,然后进行性能的测试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 输入:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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,目标是减少内存分配和数据拷贝次数。  

... ...

然后经过一系列漫长的优化,最后性能对比标准库:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 看需要对反射和拷贝的方向进行优化:

7. 后续

(1)代码已经开源:https://github.com/linkxzhou/mylib/tree/master/go/sjson (2)继续性能优化,尝试探索让 AI 如何对项目进行性能优化和构建 go pprof MCP Server

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-05-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 周末程序猿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 选用工具
  • 2. Prompt
  • 3. 限制文件大小
  • 4. 提供方向性的指引
  • 5. 充分的测试
  • 6. 性能优化
  • 7. 后续
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档