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

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

作者头像
用户1904552
发布于 2025-05-09 04:35:51
发布于 2025-05-09 04:35:51
7900
代码可运行
举报
文章被收录于专栏:周末程序猿周末程序猿
运行总次数: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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
基于 JIT 技术的开源全场景高性能 JSON 库
大家好,我是Mandy,上一节我们对Go中的切片数据类型进行了深度的剖析,今天给大家分享一个字节跳动自研开源的JSON数据解析包。一个速度奇快的 JSON 序列化/反序列化库,由 JIT (即时编译)和 SIMD (单指令流多数据流)加速。
兔云小新LM
2023/08/09
7330
基于 JIT 技术的开源全场景高性能 JSON 库
我是如何实现Go性能5倍提升的?
代码的稳健、可读和高效是每一位 coder 的共同追求,写出更高效的代码不仅让自己爽、让 reviewer 赏心悦目,更能对业务带来实际的正面影响。本文将从实践及源码层面对 Go 的高性能编程进行解析,带你进入 Go 的高性能世界。
腾讯云开发者
2024/01/04
2.3K1
我是如何实现Go性能5倍提升的?
超好用的golang工具分享
go-callvis是一个代码调用关系的可视化工具,它可以帮助我们了解指定项目代码的结构,以达到更快的理解代码意图的目的。
于顾而言SASE
2024/03/21
1790
超好用的golang工具分享
Go语言——测试与性能
​ 作为一名合格的开发者,不应该在程序开发完之后才开始写测试代码。使用 Go 语言的测试 框架,可以在开发的过程中就进行单元测试和基准测试。和 go build 命令类似,go test 命 令可以用来执行写好的测试代码,需要做的就是遵守一些规则来写测试。而且,可以将测试无缝 地集成到代码工程和持续集成系统里。
传说之下的花儿
2023/04/16
1.2K0
Go每日一库之79:testing
testing是 Go 语言标准库自带的测试库。在 Go 语言中编写测试很简单,只需要遵循 Go 测试的几个约定,与编写正常的 Go 代码没有什么区别。Go 语言中有 3 种类型的测试:单元测试,性能测试,示例测试。下面依次来介绍。
luckpunk
2025/01/18
720
Go通过19:单元测试,确保高质量代码的秘诀!
您诸位好啊,我是无尘,今天我们进入到Go语言单元测试阶段,讲讲Go如何进行单元测试。
微客鸟窝
2021/08/18
5520
Go通过19:单元测试,确保高质量代码的秘诀!
Go Reflect 性能
Go reflect包提供了运行时获取对象的类型和值的能力,它可以帮助我们实现代码的抽象和简化,实现动态的数据获取和方法调用, 提高开发效率和可读性, 也弥补Go在缺乏泛型的情况下对数据的统一处理能力。
李海彬
2019/03/07
1K0
Go Reflect 性能
如何打造高性能的 Go 缓存库
我在看一些优秀的开源库的时候看到一个有意思的缓存库 fastcache,在它的介绍主要有以下几点特点:
luozhiyun
2021/05/16
1.3K0
python接口自动化测试 - unittest框架基本使用
这是比较常见的断言方式,当然还有一些比较容易理解的断言方式就没有一一举例啦,具体可以看看下面列表
小菠萝测试笔记
2020/06/09
6500
PHP转Go速学手册
整理了一份简要的手册,帮助大家高效的上手Go语言,主要是通过对比PHP和Go的不同点来强化理解,内容主要分为以下四部分:
用户1093396
2021/07/28
2.4K0
9.Go编程快速入门学习
描述: 日常开发中, 测试是不能缺少的. 通常国内的程序员都不太关注单元测试这一部分, 俗话说不写测试的开发不是好程序猿,我认为每一位开发者都应该了解 TDD(Test Driven Development-测试驱动开发),所以本章将主要介绍下在Go语言中如何做单元测试和基准测试。
全栈工程师修炼指南
2022/09/29
7230
9.Go编程快速入门学习
几百行代码实现一个 JSON 解析器
之前在写 gscript 时我就在想有没有利用编译原理实现一个更实际工具?毕竟真写一个语言的难度不低,并且也很难真的应用起来。
crossoverJie
2022/10/27
4680
几百行代码实现一个 JSON 解析器
ChatGPT|AI自制编程语言-实现JavaScript编译器
去年的这个时候一直在探索如何用 AI 实现编程语言,当时 Agent 和工具链还不够齐全,所以尝试一段时间就断更了,有兴趣的可以再回忆这两篇用 Prompt 实现的词法解析器:
用户1904552
2025/04/27
1390
ChatGPT|AI自制编程语言-实现JavaScript编译器
Go 语言原生的 json 包有什么问题?如何更好地处理 JSON 数据?
Go 的 “玩家” 们看到这个题目可能会很疑惑——对于 JSON 而言,Go 原生库 encoding/json 已经是提供了足够舒适的 JSON 处理工具,广受 Go 开发者的好评。它还能有什么问题?但是,实际上在业务开发过程中,我们遇到了不少原生 json 做不好甚至是做不到的问题,还真是不能完全满足我们的要求。
amc
2021/05/06
5.3K0
Go 语言原生的 json 包有什么问题?如何更好地处理 JSON 数据?
Go 高性能系列教程之一:基准测试
要想改进程序的性能,首先要知道程序的当前性能。 本节主要关注使用 Go testing 包如何构建有用的基准测试,并且给出一些最佳实践以及常见的陷阱。
Go学堂
2023/01/31
7670
PHPUnit简介及使用
1、它是一款轻量级的PHP测试框架,地址:http://www.phpunit.cn
双面人
2020/02/25
1.7K0
for-loop 与 json.Unmarshal 性能分析概要
在项目中,常常会遇到循环交换赋值的数据处理场景,尤其是 RPC,数据交互格式要转为 Protobuf,赋值是无法避免的。一般会有如下几种做法:
李海彬
2019/05/08
1.1K0
for-loop 与 json.Unmarshal 性能分析概要
试用GO开发pyhton编译器:字节码基础
掌握一门编程语言最好的办法或许是将它的编译器设计出来。毫无疑问那些开发Python编译器的人应该是世界上对Python了解最深刻的人群之一。我用python开发过不少程序,但是每次反思或复盘的时候总是感觉对Python的认知还不到位,由此也看了很多讲Python的书,但看的时候感觉好像懂了,但过了一段时间后又忘了,也就是说单纯看书很难将某一项技术完全内化。当然技能的掌握必然要从实践中来,但是我发现在使用Python开发程序时,我总是使用它的一部分功能就够了,或者说居于我的思维模式限制,我在使用python开发时总是落入一个套路,这使得我只能掌握python技术的冰山一角,就如同井底之蛙一样只了解一小块内容,为了能够打破认知局限,让我自己能更全面的对python的设计原理有更深入的了解,我打算尝试做一个能运行的python编译器。
望月从良
2021/12/20
3740
试用GO开发pyhton编译器:字节码基础
Golang UnitTest单元测试
单元测试是程序开发者适用一段代码来验证另外一段代码写的是否符合预期的一种相对高效的自我测试方法。
黑光技术
2019/03/06
8.9K0
Golang UnitTest单元测试
如何在Go语言中进行优雅的单元测试
作为开发者,保持使用优雅的测试用例可以带来多方面的好处,这些好处不仅限于提高代码质量,还涉及到团队协作、项目可维护性、以及长期的技术债务管理等方面。
闫同学
2024/09/14
2720
相关推荐
基于 JIT 技术的开源全场景高性能 JSON 库
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验