编辑
2023-11-01
测试平台
00
请注意,本文编写于 179 天前,最后修改于 178 天前,其中某些信息可能已经过时。

目录

1.背景
2.耗时和内存测试
2.1 运行1次的耗时和内存
2.2 运行1000次的耗时和内存
3.性能分析
3.1 cProfile对比
3.1.1 jsonpath
3.1.2 gjson
3.1.3 jsonpath_ng
3.1.4 jmespath
3.1.5 cProfile结论
3.2 火焰图
4.总结
5.附录

1.背景

在接口测试时,绝大部分的响应都json格式,json的取值就一定绕不过jsonpath.
某天,就在技术群讨论了jsonpath相关的问题.
讨论归讨论,结果最终还是的看实际代码怎么样.
接下来,将会实测python中几个常用jsonpath库(jsonpath,jsonpath-ng,jmespath,gjson)性能到底如何

image.png

2.耗时和内存测试

2.1 运行1次的耗时和内存

shell
/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,不管内存占用还是耗时都是跟另外三个相差甚远

2.2 运行1000次的耗时和内存

shell
/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不相伯仲

3.性能分析

3.1 cProfile对比

下面分别对比这四个库,json执行一次jsonpath取值,涉及函数调用次数和原始调用耗时
完整的调用统计非常多,只放出调用次数较多的部分,按照调用次数到序排序

3.1.1 jsonpath

在cProfile的输出中,ncalls列表示函数被调用的次数。看到219/196这意味着函数被递归地调用了。
第一个数字219表示函数被调用的总次数。
第二个数字196表示函数被非递归地调用的次数。

shell
/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)

3.1.3 jsonpath_ng

shell
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}

3.1.4 jmespath

shell
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)

3.1.5 cProfile结论

cProfile结果和上面的耗时和结果大体是吻合的
很明显的看到jsonpath_ng取值一次居然涉及到了24933次函数调用
而jsonpath和gjson都是一千多次,jmespath最好,仅仅需要163次,遥遥领先!

还是有一个疑惑,上面1000次耗时和内存测试,发现jmespath,jsonpath,gjson是不相伯仲的
但jmespath函数调用确实比另外两个相差了至少1000次,结果似乎对不上

把运行次数拉大到10000次,结果就很明显了,确实是jmespath更强

shell
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次的测试结果,因为太消耗时间了,被我手动终止...

3.2 火焰图

四个库同时运行1000次,因为jsonpath_ng占用时间太大了
可以看到,大部分的耗时都是在这个函数parse_token_stream(isonpath_ng/parser.py:47)
如果需要优化,可以从这个地方着手 image.png

4.总结

通过以上的测试和分析,我们可以得出以下结论:

  1. 在耗时和内存占用方面,jsonpath-ng的表现是最差的,与其它三个库相比,无论是运行1次还是1000次,jsonpath-ng的耗时和内存占用都是最高的。因此,在性能要求较高的场景下,jsonpath-ng不是一个好的选择。

  2. jmespath在运行1次和1000次的耗时和内存占用都表现较好,且cProfile的结果显示jmespath的函数调用次数也是最少的,因此,jmespath是性能最好的一个选项。

  3. jsonpathgjson在耗时和内存占用方面表现相近,且都优于jsonpath-ng。在函数调用次数方面,jsonpathgjson也相近,但略多于jmespath

综上所述,如果你在寻找一个性能优越的jsonpath库,jmespath是最佳选择。而jsonpathgjson则是性能较为中等的选项,jsonpath-ng则是最不推荐的选项。

5.附录

本次测试的所有代码和用到的数据都会可以直接在我的GitHub仓库看到 https://github.com/lihuacai168/jsonpath_compare

本文作者:花菜

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!