【问题标题】:jq slow to join() medium size arrayjq 慢加入()中型数组
【发布时间】:2019-12-10 09:01:00
【问题描述】:

我正在尝试 join() 一个相对较大的对象数组(20k 个元素),其中包含一个字符(在这种特殊情况下为“\n”)。我预先进行了一些操作,大约在 8 秒内解决(可以接受),但是当我尝试 '| join("\n")' 最后运行时跳转到 3+ 分钟。

有什么理由让 join() 这么慢吗?是否有另一种方法可以在没有 join() 的情况下获得相同的输出?

我目前正在使用 jq-1.5(最新稳定版)

这是 JQ 文件

json2csv.jq

def json2csv:
    def tonull: if . == "null" then null else . end;
    (.[0] | keys) as $headers |
    [(
        $headers | join("\t")
    ), (
        [ .[] as $row | [ $headers[] as $h | $row[$h] | tostring | tonull ] | join("\t") ] | join("\n")
    )] | join("\n")
;

json2csv

考虑:

$ jq 'length' test.json 
23717

有了脚本是我想要的(并放在上面)

$ time jq -rf json2csv.jq test.json > test.csv

real    3m46.721s
user    1m48.660s
sys     1m57.698s

用同样的脚本,去掉join("\n")

$ time jq -rf json2csv.jq test.json > test.csv

real    0m8.564s
user    0m8.301s
sys     0m0.242s

(注意:我删除了第二个连接,因为否则 JQ 无法聚合一个数组和一个字符串,这是有道理的(但这只是在一个包含 2 个元素的数组上,所以第二个连接不是问题))

【问题讨论】:

    标签: json jq processing-efficiency


    【解决方案1】:

    您根本不需要使用 join。与其考虑将整个文件转换为单个字符串,不如将其视为将每一行转换为字符串。 jq 输出结果流的方式最终会为您提供所需的结果(假设您采用原始输出)。

    试试类似的东西。

    def json2csv:
        def tonull: if . == "null" then null else . end;
        (.[0] | keys) as $headers
        # output headers followed by rows of values as arrays
         |  (
                $headers
            ),
            (
                .[] | [ .[$headers[]] | tostring | tonull ]
            )
        # convert the arrays to tab separated values strings
         |  @tsv
         ;
    

    【讨论】:

    • 我不能使用它,因为 @tsv jq 命令对什么是“值”非常严格,遗憾的是
    • 如果您觉得@tsv 不适合您,您仍然可以使用join("\t"),因为您已经这样做了。只要知道您将不得不根据需要自己处理转义值。这里的重点是您只为每一行生成字符串,而不是整个最终结果。
    【解决方案2】:

    想了想,如果你扫描一个数组(.[]),jq 会自动显示回车符('\n'),这意味着在这种特殊情况下我可以这样做:

    def json2csv:
        def tonull: if . == "null" then null else . end;
        (.[0] | keys) as $headers |
        [(
            $headers | join("\t")
        ), (
            [ .[] as $row | [ $headers[] as $h | $row[$h] | tostring | tonull ] | join("\t") ] | .[]
        )] | .[]
    ;
    
    json2csv
    

    这解决了我的问题

    time jq -rf json2csv.jq test.json > test.csv
    
    real    0m6.725s
    user    0m6.454s
    sys     0m0.245s
    

    我把问题留了下来,好像我想使用除 '\n' 之外的任何其他字符,这不会解决问题。

    【讨论】:

    • 为什么不直接将数组传递给@tsv
    【解决方案3】:

    在生成 CSV 或 TSV 等输出时,我们的想法是尽可能多地流式传输数据。您要做的最后一件事是在包含所有数据的数组上运行join。如果您确实想使用 \n 以外的分隔符,则将其添加到流中的每个项目,然后使用 -j 命令行选项。

    另外,我认为您的诊断可能不太正确,因为加入包含大量小字符串的数组非常快。下面是比较连接一个包含两个字符串和一个包含 100,000 个字符串的数组的时序。如果你想知道的话,我的机器很慢。

    ./join.sh 2
    3
    real         0.03
    user         0.02
    sys          0.00
       1896448  maximum resident set size
    
    $ ./join.sh 100000
    588889
    real         2.20
    user         2.05
    sys          0.13
      21188608  maximum resident set size
    
    
    $cat join.sh
    #!/bin/bash
    /usr/bin/time -lp jq -n --argjson n "$1" '[range(0;$n)|tostring]|join(".")|length'
    

    以上运行使用 jq 1.6,但使用 jq 1.5 会产生非常相似的结果。

    另一方面,加入大量 (20,000) 个非常长的字符串 (1K) 明显很慢,因此显然当前的 jq 实现不是为此类操作而设计的。

    【讨论】:

    • 我认为你说得对,我的字符串有好几K长... -j 选项有什么作用?它在 jq 1.5 上不存在
    • jq-1.5 --help 没有宣传它,但 1.5 版确实支持它。 -j 选项只是抑制通常在 JSON 实体之间提供的换行符。请参阅 jq 手册。
    猜你喜欢
    • 2022-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-26
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多