【发布时间】:2020-10-30 16:53:39
【问题描述】:
用例
我需要将 JSON 数据的大文件 (~5G) 拆分为带有 newline-delimited JSON 的较小文件,以节省内存(即,无需将整个 JSON blob 读入内存)。每个源文件中的 JSON 数据是一个对象数组。
很遗憾,源数据不是 newline-delimited 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 来达到预期的结果,如SO Post 中所述。多亏了jq streaming parser,这种方法的内存效率很高。下面是实现预期结果的命令:
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 与 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
问题
- 与
sed相比,jq的处理速度是否会变慢?有道理jq会慢一些,因为它在后台进行了大量验证,但慢 4 倍似乎并不正确。 - 我能做些什么来提高
jq处理这个文件的速度吗?我更喜欢使用jq来处理文件,因为我相信它可以无缝处理其他行输出格式,但鉴于我每天要处理数千个文件,很难证明我观察到的速度差异是合理的。
【问题讨论】:
-
jq 的“流式解析器”被认为很慢(并且可能比需要的慢),但 jq 版本之间可能存在差异。在 jq 领域内,最好的办法是确定哪个 jq 版本最慢。如果您有时间,值得检查 1.5、1.6 和
master版本。不用说,如果您每天要处理这么多文件,那么尝试以合理的格式为您提供这些文件可能是值得的。 -
@dmitry - jtc 能帮上忙吗?
-
对
sed命令的一些建议:避免使用cat,没有任何用处并且占用时间...如果输入为ASCII,则使用LC_ALL=C,将大大加快...如果输入不'根本没有换行符,使用GNU awk会更好,因为它可以使用自定义记录分隔符...awk -v RS='}, ' -v ORS='}\n' 'NR==1{sub(/^\[/, "")} RT; END{sub(/]\n/, "\n"); printf "%s", $0}'(同样,如果输入为ASCII,则使用LC_ALL=C) -
@peak 我将检查
1.5、1.6和master,然后报告。我完全同意以合理的格式提供文件。这些来自我们与另一家公司管理的 API 集成。他们知道文件格式问题,希望他们能尽快解决这个问题,但与此同时,我们需要处理现有的问题。 -
@peak,我打算只在下一个版本中在
jtc中添加streamed parsing(它会是多线程的,所以我希望它会是快),所以目前不可能在jtc中以内存高效的方式做到这一点:(