在接口测试时,绝大部分的响应都 json 格式,json 的取值就一定绕不过 jsonpath.
某天,就在技术群讨论了 jsonpath 相关的问题.
讨论归讨论,结果最终还是的看实际代码怎么样.
接下来,将会实测 python 中几个常用 jsonpath 库 (jsonpath,jsonpath-ng,jmespath,gjson
) 性能到底如何
/Users/rikasai/.virtualenvs/jsonpath_compare/bin/python /Users/rikasai/code/python/jsonpath_compare/main.py
test_jsonpath 总耗时: 0.002014 秒
test_jsonpath 使用了 0.038136 MB 内存,峰值为 0.039424 MB
test_gjson 总耗时: 0.001517 秒
test_gjson 使用了 0.023019 MB 内存,峰值为 0.028319 MB
test_jsonpath_ng 总耗时: 0.027746 秒
test_jsonpath_ng 使用了 0.2931 MB 内存,峰值为 0.323259 MB
test_jmespath 总耗时: 0.000571 秒
test_jmespath 使用了 0.022307 MB 内存,峰值为 0.025619 MB
运行一次的结果大概已经能看到 jsonpath_ng,不管内存占用还是耗时都是跟另外三个相差甚远
/Users/rikasai/.virtualenvs/jsonpath_compare/bin/python /Users/rikasai/code/python/jsonpath_compare/main.py
test_jsonpath 总耗时: 0.126801 秒
test_jsonpath 使用了 0.218614 MB 内存,峰值为 2.219974 MB
test_gjson 总耗时: 0.264562 秒
test_gjson 使用了 0.108182 MB 内存,峰值为 2.135345 MB
test_jsonpath_ng 总耗时: 17.054877 秒
test_jsonpath_ng 使用了 3.061619 MB 内存,峰值为 6.086255 MB
test_jmespath 总耗时: 0.137950 秒
test_jmespath 使用了 0.137203 MB 内存,峰值为 1.973523 MB
1 次可能说明不了问题,运行 1000 次, 跟运行 1 次时结果是类似.
可以看到 jsonpath_ng 相比另外三个要,耗时和内存占用都是非常离谱,完全不应该放在一起比较那种
另外三个 jmespath,jsonpath,gjson 不相伯仲
下面分别对比这四个库,json 执行一次 jsonpath 取值,涉及函数调用次数和原始调用耗时
完整的调用统计非常多,只放出调用次数较多的部分,按照调用次数到序排序
在 cProfile 的输出中,ncalls 列表示函数被调用的次数。看到 219/196 这意味着函数被递归地调用了。
第一个数字 219 表示函数被调用的总次数。
第二个数字 196 表示函数被非递归地调用的次数。/Users/rikasai/.virtualenvs/jsonpath_compare/bin/python /Users/rikasai/code/python/jsonpath_compare/main.py 1417 function calls (1350 primitive calls) in 0.001 seconds
Ordered by: call count
ncalls tottime percall cumtime percall filename:lineno(function)
251 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
219/196 0.000 0.000 0.000 0.000 {built-in method builtins.len}
160 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance}
119 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:164(getitem)
61 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:233(next)
56 0.000 0.000 0.000 0.000 {built-in method builtins.min}
50 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:160(len__)
46 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:254(get)
33 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:172(append)
33 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:249(match)
25 0.000 0.000 0.000 0.000 {built-in method builtins.ord}
24/8 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:174(getwidth)
### 3.1.2 gjson
```shell
1167 function calls (1088 primitive calls) in 0.001 seconds
Ordered by: call count
ncalls tottime percall cumtime percall filename:lineno(function)
176 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
164/146 0.000 0.000 0.000 0.000 {built-in method builtins.len}
127 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance}
59 0.000 0.000 0.000 0.000 {method 'startswith' of 'str' objects}
52 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:164(__getitem__)
42 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:233(__next)
34 0.000 0.000 0.000 0.000 {built-in method builtins.min}
27 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:254(get)
26 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:160(__len__)
26 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:249(match)
24933 function calls (24438 primitive calls) in 0.013 seconds
Ordered by: call count
ncalls tottime percall cumtime percall filename:lineno(function)
3668 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}
3634 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
2528 0.000 0.000 0.000 0.000 {built-in method builtins.id}
2241 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}
1623/1420 0.000 0.000 0.000 0.000 {built-in method builtins.len}
984 0.001 0.000 0.002 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/ply/yacc.py:2165(lr0_goto)
897 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:233(__next)
850 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance}
795 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/ply/yacc.py:127(__getattribute__)
795 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/ply/yacc.py:130(__call__)
622 0.000 0.000 0.000 0.000 {method 'match' of 're.Pattern' objects}
386 0.000 0.000 0.000 0.000 /Users/rikasai/.pyenv/versions/3.9.11/lib/python3.9/sre_parse.py:164(__getitem__)
299 0.000 0.000 0.000 0.000 {built-in method builtins.min}
163 function calls (154 primitive calls) in 0.000 seconds
Ordered by: call count
ncalls tottime percall cumtime percall filename:lineno(function)
28 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/jmespath/lexer.py:129(_next)
14 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/jmespath/parser.py:463(_current_token)
12 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/jmespath/lexer.py:26(tokenize)
11 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects}
11 0.000 0.000 0.000 0.000 {built-in method builtins.getattr}
10 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/jmespath/parser.py:460(_advance)
8/1 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/jmespath/visitor.py:87(visit)
6 0.000 0.000 0.000 0.000 {built-in method builtins.len}
6 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/jmespath/parser.py:469(_lookahead_token)
4 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/jmespath/parser.py:466(_lookahead)
3/1 0.000 0.000 0.000 0.000 /Users/rikasai/.virtualenvs/jsonpath_compare/lib/python3.9/site-packages/jmespath/parser.py:118(_expression)
cProfile 结果和上面的耗时和结果大体是吻合的
很明显的看到 jsonpath_ng 取值一次居然涉及到了 24933 次函数调用
而 jsonpath 和 gjson 都是一千多次,jmespath 最好,仅仅需要 163 次,遥遥领先!
还是有一个疑惑,上面 1000 次耗时和内存测试,发现 jmespath,jsonpath,gjson 是不相伯仲的
但 jmespath 函数调用确实比另外两个相差了至少 1000 次,结果似乎对不上
把运行次数拉大到 10000 次,结果就很明显了,确实是 jmespath 更强
test_jsonpath 总耗时: 1.746682 秒
test_jsonpath 使用了 0.210114 MB 内存,峰值为 19.831589 MB
test_gjson 总耗时: 2.730377 秒
test_gjson 使用了 0.184749 MB 内存,峰值为 18.914425 MB
test_jmespath 总耗时: 1.194248 秒
test_jmespath 使用了 0.127859 MB 内存,峰值为 18.852175 MB
为什么这里 jsonpath_ng 没有 10000 次的测试结果,因为太消耗时间了,被我手动终止...
四个库同时运行 1000 次,因为 jsonpath_ng 占用时间太大了
可以看到,大部分的耗时都是在这个函数parse_token_stream(isonpath_ng/parser.py:47)
如果需要优化,可以从这个地方着手
通过以上的测试和分析,我们可以得出以下结论:
在耗时和内存占用方面,jsonpath-ng
的表现是最差的,与其它三个库相比,无论是运行 1 次还是 1000 次,jsonpath-ng
的耗时和内存占用都是最高的。因此,在性能要求较高的场景下,jsonpath-ng
不是一个好的选择。
jmespath
在运行 1 次和 1000 次的耗时和内存占用都表现较好,且 cProfile 的结果显示jmespath
的函数调用次数也是最少的,因此,jmespath
是性能最好的一个选项。
jsonpath
和gjson
在耗时和内存占用方面表现相近,且都优于jsonpath-ng
。在函数调用次数方面,jsonpath
和gjson
也相近,但略多于jmespath
。
综上所述,如果你在寻找一个性能优越的 jsonpath 库,jmespath
是最佳选择。而jsonpath
和gjson
则是性能较为中等的选项,jsonpath-ng
则是最不推荐的选项。
本次测试的所有代码和用到的数据都会可以直接在我的 GitHub 仓库看到
https://github.com/lihuacai168/jsonpath_compare