json - 使用 jq 处理大文件时提高性能
问题描述
用例
我需要以一种内存有效的方式(即,无需将整个 JSON blob 读入内存)将~5G
JSON 数据的大文件(每个源文件中的 JSON 数据是一个对象数组。
不幸的是,源数据不是 换行符分隔的 JSON,在某些情况下,文件中根本没有换行符。这意味着我不能简单地使用该split
命令通过换行符将大文件拆分为较小的块。以下是源数据如何存储在每个文件中的示例:
带有换行符的源文件示例。
[{"id": 1, "name": "foo"}
,{"id": 2, "name": "bar"}
,{"id": 3, "name": "baz"}
...
,{"id": 9, "name": "qux"}]
没有换行符的源文件示例。
[{"id": 1, "name": "foo"}, {"id": 2, "name": "bar"}, ...{"id": 9, "name": "qux"}]
以下是单个输出文件所需格式的示例:
{"id": 1, "name": "foo"}
{"id": 2, "name": "bar"}
{"id": 3, "name": "baz"}
当前解决方案
我可以通过使用jq
和split
如this SO Post中所述来实现所需的结果。由于jq
流解析器,这种方法具有内存效率。这是实现所需结果的命令:
cat large_source_file.json \
| jq -cn --stream 'fromstream(1|truncate_stream(inputs))' \
| split --line-bytes=1m --numeric-suffixes - split_output_file
问题
上面的命令需要~47 mins
处理整个源文件。这似乎很慢,特别是与sed
它相比可以更快地产生相同的输出。
jq
以下是一些性能基准,用于显示vs.的处理时间sed
。
export SOURCE_FILE=medium_source_file.json # smaller 250MB
# using jq
time cat ${SOURCE_FILE} \
| jq -cn --stream 'fromstream(1|truncate_stream(inputs))' \
| split --line-bytes=1m - split_output_file
real 2m0.656s
user 1m58.265s
sys 0m6.126s
# using sed
time cat ${SOURCE_FILE} \
| sed -E 's#^\[##g' \
| sed -E 's#^,\{#\{#g' \
| sed -E 's#\]$##g' \
| sed 's#},{#}\n{#g' \
| split --line-bytes=1m - sed_split_output_file
real 0m25.545s
user 0m5.372s
sys 0m9.072s
问题
jq
与 相比,这种较慢的处理速度是预期的sed
吗?考虑到它jq
在后台进行了大量验证,所以速度会慢一些,但慢 4 倍似乎并不正确。- 我能做些什么来提高
jq
处理这个文件的速度吗?我更喜欢用它jq
来处理文件,因为我相信它可以无缝处理其他行输出格式,但鉴于我每天要处理数千个文件,很难证明我观察到的速度差异是合理的。
解决方案
jq 的流解析器(使用 --stream 命令行选项调用的解析器)故意牺牲速度以减少内存需求,如下面的指标部分所示。一个达到不同平衡的工具(似乎更接近您正在寻找的工具)是jstream
,其主页是 https://github.com/bcicen/jstream
在 bash 或类似 bash 的 shell 中运行命令序列:
cd
go get github.com/bcicen/jstream
cd go/src/github.com/bcicen/jstream/cmd/jstream/
go build
将产生一个可执行文件,您可以像这样调用它:
jstream -d 1 < INPUTFILE > STREAM
假设 INPUTFILE 包含一个(可能是巨大的)JSON 数组,上面的行为类似于 jq's .[]
,带有 jq 的 -c (紧凑)命令行选项。事实上,如果 INPUTFILE 包含 JSON 数组流,或者 JSON 非标量流,情况也是如此……
说明性时空度量
概括
对于手头的任务(流式传输数组的顶级项):
mrss u+s
jq --stream: 2 MB 447
jstream : 8 MB 114
jq : 5,582 MB 39
用一句话来说:
space
: jstream 节省内存,但不如 jq 的流解析器。time
:jstream 的运行速度比 jq 的常规解析器稍慢,但比 jq 的流解析器快 4 倍。
有趣的是,两个流解析器的空间*时间大致相同。
测试文件的表征
测试文件包含 10,000,000 个简单对象的数组:
[
{"key_one": 0.13888342355537053, "key_two": 0.4258700286271502, "key_three": 0.8010012924267487}
,{"key_one": 0.13888342355537053, "key_two": 0.4258700286271502, "key_three": 0.8010012924267487}
...
]
$ ls -l input.json
-rw-r--r-- 1 xyzzy staff 980000002 May 2 2019 input.json
$ wc -l input.json
10000001 input.json
jq时代和夫人
$ /usr/bin/time -l jq empty input.json
43.91 real 37.36 user 4.74 sys
4981452800 maximum resident set size
$ /usr/bin/time -l jq length input.json
10000000
48.78 real 41.78 user 4.41 sys
4730941440 maximum resident set size
/usr/bin/time -l jq type input.json
"array"
37.69 real 34.26 user 3.05 sys
5582196736 maximum resident set size
/usr/bin/time -l jq 'def count(s): reduce s as $i (0;.+1); count(.[])' input.json
10000000
39.40 real 35.95 user 3.01 sys
5582176256 maximum resident set size
/usr/bin/time -l jq -cn --stream 'fromstream(1|truncate_stream(inputs))' input.json | wc -l
449.88 real 444.43 user 2.12 sys
2023424 maximum resident set size
10000000
jstream时代和夫人
$ /usr/bin/time -l jstream -d 1 < input.json > /dev/null
61.63 real 79.52 user 16.43 sys
7999488 maximum resident set size
$ /usr/bin/time -l jstream -d 1 < input.json | wc -l
77.65 real 93.69 user 20.85 sys
7847936 maximum resident set size
10000000
推荐阅读
- java - 如何在 AdListener 的覆盖方法中更改变量值?
- c# - 搜索字符串 - 使用文本框搜索文本文件
- sql - Postgres:查询 created_at 时间相差 < 1 秒的用户的最近 2 条记录?
- javascript - 如何在按钮单击时将列表滚动到顶部?
- python - 在两个函数范围内拟合数据
- javascript - DOM外的浏览器Javascript中的自定义事件发射器/消费者
- php - 服务器关闭了phpseclib连接?
- sql - 指向新的 SQL Server 后网站停止正常工作
- c# - 使用 ASP.NET Core 启动时的默认路由
- php - PHP在上传图像时从React Native接收空数据?