LJSON:全能、高性能、真流式的 C 语言 JSON 引擎和数值引擎

项目地址

  • github: https://github.com/lengjingzju/json
  • gitee: https://gitee.com/lengjingzju/json

系列文章

1. 项目定位与设计初衷

LJSON 诞生于 2019 年 10 月,比 yyjson 早一年,初衷并非单纯追求基准测试中的极限解析速度,而是要在 性能、内存占用、可编辑性、流式能力、可维护性 之间找到最佳平衡。

它目前状态是:

  • 能在多数场景下媲美甚至超越 yyjson 的性能
  • 提供 yyjson 架构无法小改实现的真流式处理能力
  • 保持代码可读性和可扩展性,适合长期演进

2. 核心能力

双重核心

全能 JSON 处理

  • 完整支持 JSON5 规范(十六进制、注释、尾逗号、单引号等)
  • DOM / SAX 双解析模式
  • 真流式文件(边读边解析,边写边打印)

高性能数值转换引擎

  • 独创 ldouble 算法
  • 浮点 ↔ 字符串转换性能远超标准库和主流算法(sprintf、grisu2、dragonbox)
  • 精度是16位,在边界处理时追求最短而不是偶数(标准是16位或17位,取决于二进制指数转换为十进制指数的乘子的分辨率)

3. 架构与模式设计

LJSON 提供 7 种解析模式4 种打印模式,覆盖从内存到文件、从 DOM 到 SAX 的全场景需求:

解析模式

  1. DOM 经典模式(malloc/free)
  2. DOM 内存池模式
  3. DOM 复用模式(可编辑,字符串原地复用)
  4. DOM 文件流模式(真流式)
  5. DOM 文件流内存池模式(真流式 + 内存池)
  6. SAX 模式(回调处理)
  7. SAX 文件流模式(真流式 + 回调)

打印模式

  1. DOM → 字符串
  2. DOM → 文件(真流式)
  3. SAX → 字符串
  4. SAX → 文件(真流式)

真流式:边读文件边解析,边打印边写文件,无需完整读入或生成中间大缓冲,内存占用可降至常数级,即使处理 1GB JSON 文件也仅需 KB 级内存。

yyjson ≈ LJSON 的 DOM 复用模式 + 激进只读优化

4. 与 yyjson 的设计差异

核心差异包括:

维度 LJSON yyjson
模式设计 多模式(含真流式、可编辑复用) 单一复用模式(只读优化)
可编辑性 复用模式可编辑 只读与可编辑严格分离(val / mut_val)
字符串存储 标准 C 字符串(尾 1 个 \0 非标准尾 4 个 \0(减少边界判断)
对象存储 key 与 value 一起存储 key 与 value 分开存储
内存策略 按需分配,尽量避免浪费 大块预分配,冗余度高
访问加速 json_items_t 缓存,数组 O(1)、对象 O(logN) 未知是否有缓存,可能 O(N)/O(2N)
优化手法 内联、分支预测、查表、缓存元信息;保持可读性 同类优化 + 大量宏循环展开,代码晦涩
流式处理 真流式解析/打印 不支持

5. 性能来源与权衡

优化技术对比

两者都使用:

  • 算法优化
  • 内存池(块)优化
  • 内联优化(inline)
  • 分支预测优化(likely/unlikely)
  • Cache 命中优化
  • 查表优化
  • 拷贝优化
  • 信息记录优化(缓存字符串长度等)

差异:

  • yyjson:大量宏进行循环手动展开,代码紧凑但晦涩,性能更激进;使用非标准特性(尾后 4 个 \0、非对齐访问等)。
  • LJSON:追求易维护、可扩展、低占用、高性能的工程化平衡。保持可读性,宏进行手动循环展开。

性能对比

yyjson 相当于只实现了 LJSON 的DOM复用模式,并且转换可编辑模式时对象和字符串都存储在内存块里,和LJSON的内存池原理一致。

  • yyjson 只读复用模式 性能略高于 LJSON,来源于:
    • 模式单一
    • 激进资源使用(预分配冗余内存)
    • 非标准特性(尾后 4 个 \0、非对齐访问)
    • 宏循环展开
  • yyjson 只读模式 性能和 LJSON 很接近,两者互有胜负
  • yyjson 可编辑模式 性能可能大幅落后于 LJSON(大文件解析 yyjson 比 LJSON 内存占用多约一倍,速度慢约一倍

注:如果 LJSON 放弃流式、标准字符串、可编辑性,采用更晦涩代码,可达到 yyjson 只读复用模式 性能,但那将不再是 LJSON 的设计哲学。

6. 优化机制亮点

  • 零堆分配:首次分配后循环复用,避免频繁 malloc/free。
  • 原地字符串复用:直接引用输入缓冲区,减少拷贝与内存带宽消耗。
  • json_items 加速器:数组 O(1) 访问,对象 O(logN) 查找。
  • 真流式管线:解析与 I/O 并行,消除峰值内存与额外拷贝。
  • ldouble 数值引擎:浮点转字符串性能领先 sprintf 10~70 倍。

7. 多维度对比(可编辑模式)

对比维度 LJSON yyjson RapidJSON cJSON
解析性能 高(547ms,可编辑复用) 中高(1011ms,可编辑) 较高 中等
打印性能 高(真流式+ldouble) 高(全缓冲) 中等
内存占用 可常数级(流式) 较高 较高 较高
CPU 特性依赖
可扩展性
代码可读性 极低
关键机制 零堆分配、内存池复用、原地字符串、json_items、ldouble 原地复用、内存块、非标准字符串尾、循环展开 模板+分配器 malloc/free

8. 适用场景

  • 大文件处理:GB 级 JSON 格式化/压缩,内存占用可常数级。
  • 嵌入式系统:低内存、高性能需求。
  • 高频读写:实时数据流解析与生成。
  • 跨平台开发:Linux / Windows / 嵌入式 RTOS。

9. 设计哲学总结

yyjson:以极致性能为第一目标,通过激进优化手段和单一模式深耕,适合极限只读解析场景。
LJSON:追求易维护、可扩展、低占用、高性能的工程化平衡,适合通用、可编辑、流式处理、大文件低内存占用的场景。纯 C 实现,零第三方依赖,跨平台编译,接口设计参考 cJSON,逻辑清晰,编译速度快,易于二次开发。

LJSON 的价值不仅在于跑分,更在于它能在真实工程环境中,以极低的内存占用和高性能,稳定支撑多样化的 JSON 处理需求。


LJSON的ldouble算法性能测试

ldouble算法底数只有16位十进制数字精度(标准是16或17位)并且舍入是舍入到最短(标准是等距取偶)。

  • Linux: Ubuntu 20.04 (VMWare), Intel i7-1260P, gcc9.4.0
  • Windows: Windows 11, Intel i7-1260P, MSVC2023
  • dtoa测试方法: ./jnum_test <num> 10000000 (1000万次转换)
  • atod测试方法: ./jnum_test a <num> 10000000 (1000万次转换)

以下表格由Deepseek根据测试结果整理

算法对比

浮点数转字符串dtoa

1. 普通短小数(小数部分 ≤ 8位)
  • Linux
Algorithm Average Time (ms) Average Percentage (%)
sprintf 1286.33 -
ldouble 83.33 1544.44
grisu2 74.11 1737.11
dragonbox 86.00 1499.33
  • Windows
Algorithm Average Time (ms) Average Percentage (%)
sprintf 2093.11 -
ldouble 104.78 1999.89
grisu2 154.44 1355.56
dragonbox 131.11 1594.22
2. 普通长小数(小数部分 > 8位)
  • Linux
Algorithm Average Time (ms) Average Percentage (%)
sprintf 1420.25 -
ldouble 105.75 1342.50
grisu2 158.50 896.25
dragonbox 112.25 1265.00
  • Windows
Algorithm Average Time (ms) Average Percentage (%)
sprintf 2300.00 -
ldouble 120.00 1916.67
grisu2 250.00 920.00
dragonbox 150.00 1533.33
3. 整数(1e^x,指数 0 ≤ x < 20)
  • Linux
Algorithm Average Time (ms) Average Percentage (%)
sprintf 1500.94 -
ldouble 37.56 3996.81
grisu2 143.19 1048.81
dragonbox 43.81 3425.94
  • Windows
Algorithm Average Time (ms) Average Percentage (%)
sprintf 1600.00 -
ldouble 55.00 2909.09
grisu2 150.00 1066.67
dragonbox 75.00 2133.33
4. 大值科学计数法(x.xxx e100)
  • Linux
Algorithm Average Time (ms) Average Percentage (%)
sprintf 3400.00 -
ldouble 85.00 4000.00
grisu2 150.00 2266.67
dragonbox 110.00 3090.91
  • Windows
Algorithm Average Time (ms) Average Percentage (%)
sprintf 13500.00 -
ldouble 400.00 3375.00
grisu2 900.00 1500.00
dragonbox 500.00 2700.00
5. 小值科学计数法(x.xxx e-100)
  • Linux
Algorithm Average Time (ms) Average Percentage (%)
sprintf 2100.00 -
ldouble 90.00 2333.33
grisu2 160.00 1312.50
dragonbox 110.00 1909.09
  • Windows
Algorithm Average Time (ms) Average Percentage (%)
sprintf 2900.00 -
ldouble 200.00 1450.00
grisu2 800.00 362.50
dragonbox 500.00 580.00
6. 极大值科学计数法(指数 ≥ 300)
  • Linux
Algorithm Average Time (ms) Average Percentage (%)
sprintf 3600.00 -
ldouble 80.00 4500.00
grisu2 85.00 4235.29
dragonbox 90.00 4000.00
  • Windows
Algorithm Average Time (ms) Average Percentage (%)
sprintf 71000.00 -
ldouble 350.00 20285.71
grisu2 550.00 12909.09
dragonbox 400.00 17750.00
7. 极小值科学计数法(指数 ≤ -300 且 > -308)
  • Linux
Algorithm Average Time (ms) Average Percentage (%)
sprintf 2600.00 -
ldouble 75.00 3466.67
grisu2 90.00 2888.89
dragonbox 85.00 3058.82
  • Windows
Algorithm Average Time (ms) Average Percentage (%)
sprintf 6000.00 -
ldouble 300.00 2000.00
grisu2 550.00 1090.91
dragonbox 400.00 1500.00
8. 非规格数(指数 ≤ -308)
  • Linux
Algorithm Average Time (ms) Average Percentage (%)
sprintf 2700.00 -
ldouble 65.00 4153.85
grisu2 110.00 2454.55
dragonbox 60.00 4500.00
  • Windows
Algorithm Average Time (ms) Average Percentage (%)
sprintf 6000.00 -
ldouble 280.00 2142.86
grisu2 500.00 1200.00
dragonbox 350.00 1714.29
总结说明
  • 以上平均值基于每个维度中的多个输入计算得出,但由于输入数量众多,平均值可能略有偏差,但总体趋势一致。
  • Linux系统下,ldouble、grisu2和dragonbox算法均比sprintf快得多,百分比提升通常在1500%以上。
  • Windows系统下,算法性能提升同样显著,但绝对时间通常比Linux长,可能与系统差异有关。

字符串转浮点数atod

1. 普通短小数(小数部分 ≤ 8位)
系统 算法 平均时间(ms) 平均百分比(%)
Linux strtod 235.2 -
Linux ldouble 59.4 396.0
Windows strtod 482.8 -
Windows jnum_atod 60.3 801.0
2. 普通长小数(小数部分 > 8位)
系统 算法 平均时间(ms) 平均百分比(%)
Linux strtod 419.3 -
Linux ldouble 105.8 396.3
Windows strtod 868.3 -
Windows jnum_atod 113.8 763.0
3. 整数(1e^x,0 ≤ x < 20)
系统 算法 平均时间(ms) 平均百分比(%)
Linux strtod 203.1 -
Linux ldouble 86.5 234.8
Windows strtod 348.1 -
Windows jnum_atod 84.9 410.0
4. 大值科学计数法(x.xxx e100)
系统 算法 平均时间(ms) 平均百分比(%)
Linux strtod 550.7 -
Linux ldouble 141.1 390.3
Windows strtod 764.5 -
Windows jnum_atod 140.5 544.1
5. 小值科学计数法(x.xxx e-100)
系统 算法 平均时间(ms) 平均百分比(%)
Linux strtod 796.3 -
Linux ldouble 136.9 581.7
Windows strtod 1317.7 -
Windows jnum_atod 141.3 932.5
6. 极大值科学计数法(指数 ≥ 300)
系统 算法 平均时间(ms) 平均百分比(%)
Linux strtod 624.5 -
Linux ldouble 107.3 582.0
Windows strtod 828.2 -
Windows jnum_atod 113.0 732.9
7. 极小值科学计数法(指数 ≤ -300 且 > -308)
系统 算法 平均时间(ms) 平均百分比(%)
Linux strtod 1015.5 -
Linux ldouble 107.5 944.7
Windows strtod 1923.9 -
Windows jnum_atod 112.9 1704.5
8. 非规格数(指数 ≤ -308)
系统 算法 平均时间(ms) 平均百分比(%)
Linux strtod 1089.5 -
Linux ldouble 111.5 977.1
Windows strtod 1972.3 -
Windows jnum_atod 115.4 1708.9
总结分析
  1. 性能提升显著: 在所有测试类别中,jnum_atod/ldouble算法相比标准strtod都有显著性能提升,平均提升幅度在234%-1708%之间。
  2. 系统差异: Windows平台下的strtod实现相比Linux平台通常更慢,这使得jnum_atod在Windows上的相对性能提升更加明显。
  3. 数值类型影响:
    • 对于常规数字(短小数、整数),性能提升约在2-8倍
    • 对于科学计数法表示的数字,性能提升更为显著,尤其是极小值和非规格数,提升可达10-17倍
  4. 最佳表现场景: jnum_atod在处理极小值科学计数法和非规格数时表现最为出色,在Windows平台上性能提升超过17倍。
  5. 一致性: 尽管绝对时间有差异,但jnum_atod/ldouble在两个平台上都表现出相对一致的性能提升趋势。
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐