提高使用 jq 处理大文件时的性能

sal*_*l17 17 json split sed jq

用例

我需要以内存高效的方式(即,无需将整个 JSON blob 读入内存)~5G将 JSON 数据的大文件 ( )拆分为带有换行符分隔的 JSON 的较小文件。每个源文件中的 JSON 数据是一个对象数组。

不幸的是,源数据不是 换行符分隔的 JSON,在某些情况下,文件中根本没有换行符。这意味着我不能简单地使用该split命令通过换行符将大文件拆分为较小的块。以下是源数据如何存储在每个文件中的示例:

带有换行符的源文件示例。

[{"id": 1, "name": "foo"}
,{"id": 2, "name": "bar"}
,{"id": 3, "name": "baz"}
...
,{"id": 9, "name": "qux"}]
Run Code Online (Sandbox Code Playgroud)

没有换行符的源文件示例。

[{"id": 1, "name": "foo"}, {"id": 2, "name": "bar"}, ...{"id": 9, "name": "qux"}]
Run Code Online (Sandbox Code Playgroud)

以下是单个输出文件所需格式的示例:

{"id": 1, "name": "foo"}
{"id": 2, "name": "bar"}
{"id": 3, "name": "baz"}
Run Code Online (Sandbox Code Playgroud)

当前解决方案

我能够通过使用jqsplit如本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
Run Code Online (Sandbox Code Playgroud)

问题

上面的命令需要~47 mins处理整个源文件。这看起来很慢,尤其是与sed可以更快地产生相同输出的相比。

以下是一些性能基准,用于显示jqsed.

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
Run Code Online (Sandbox Code Playgroud)

问题

  1. jq与 相比,这是较慢的处理速度sed吗?jq考虑到它在幕后进行了大量验证,速度会更慢是有道理的,但慢 4 倍似乎并不正确。
  2. 我可以做些什么来提高jq处理此文件的速度?我更喜欢用jq来处理文件,因为我相信它可以无缝处理其他行输出格式,但鉴于我每天处理数千个文件,很难证明我观察到的速度差异是合理的。

pea*_*eak 6

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
Run Code Online (Sandbox Code Playgroud)

将产生一个可执行文件,您可以像这样调用它:

jstream -d 1 < INPUTFILE > STREAM
Run Code Online (Sandbox Code Playgroud)

假设 INPUTFILE 包含一个(可能是巨大的)JSON 数组,上面的行为就像 jq 的一样.[],带有 jq 的 -c(紧凑)命令行选项。事实上,如果 INPUTFILE 包含 JSON 数组流,或 JSON 非标量流,情况也是如此……

说明性的时空指标

概括

对于手头的任务(流式传输数组的顶级项目):

                  mrss   u+s
jq --stream:      2 MB   447
jstream    :      8 MB   114
jq         :  5,582 MB    39
Run Code Online (Sandbox Code Playgroud)

用一句话来说:

  1. space: jstream 在内存方面是经济的,但不如 jq 的流解析器。

  2. 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}
...
]
Run Code Online (Sandbox Code Playgroud)
$ ls -l input.json
-rw-r--r--  1 xyzzy  staff  980000002 May  2  2019 input.json

$ wc -l input.json
 10000001 input.json
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

Run Code Online (Sandbox Code Playgroud)