- 查找(get)& 修改(set):指定某种规则的查找路径(一般是 key 与 index 的集合),获取需要的那部分 JSON value 并处理。
其次,我们根据样本 JSON 的 key 数量和深度分为三个量级:
-
小(small):400B,11 key,深度 3 层;
-
中(medium):110KB,300+ key,深度 4 层(实际业务数据,其中有大量的嵌套 JSON string);
-
大(large):550KB,10000+ key,深度 6 层。
测试结果如下:
不同数据量级下 JSON 库性能表现
结果显示:目前这些 JSON 库均无法在各场景下都保持最优性能,即使是当前使用最广泛的第三方库 json-iterator,在泛型编解码、大数据量级场景下的性能也满足不了我们的需要。
JSON 库的基准编解码性能固然重要,但是对不同场景的最优匹配更关键 —— 于是我们走上了自研 JSON 库的道路。
开源库 sonic 技术原理
由于 JSON 业务场景复杂,指望通过单一算法来优化并不现实。于是在设计 sonic 的过程中,我们借鉴了其他领域/语言的优化思想(不仅限于 JSON),将其融合到各个处理环节中。其中较为核心的技术有三块:JIT、lazy-load 与 SIMD 。
JIT
对于有 schema 的定型编解码场景而言,很多运算其实不需要在“运行时”执行。这里的“运行时”是指程序真正开始解析 JSON 数据的时间段。
举个例子,如果业务模型中确定了某个 JSON key 的值一定是布尔类型,那么我们就可以在序列化阶段直接输出这个对象对应的 JSON 值(‘true’或‘false’),并不需要再检查这个对象的具体类型。
sonic-JIT 的核心思想就是:将模型解释与数据处理逻辑分离,让前者在“编译期”固定下来。
这种思想也存在于标准库和某些第三方 JSON 库,如 json-iterator 的函数组装模式:把 Go struct 拆分解释成一个个字段类型的编解码函数,然后组装并缓存为整个对象对应的编解码器(codec),运行时再加载出来处理 JSON。但是这种实现难以避免转化成大量 interface 和 function 调用栈,随着 JSON 数据量级的增长,function-call 开销也成倍放大。只有将模型解释逻辑真正编译出来