jq 大量文件性能低下

jq slow performance for numerous files

我的理解是,由于 jq 主动解析 json 流的方式,它可以大大加快 json 读取速度。但我在实践中没有看到这样的事情。这是与 R 工作流的比较(使用用 R 创建的虚拟数据)。

require(dplyr)
# also requires purrr, stringr, tibble, jsonlite

dir.create('jq-test')
setwd('jq-test')

# dummy data ----------------

dat = stringr::sentences[1:100] |> tibble::enframe(name='id', value = 'sentence') |> 
  mutate(text = purrr::map_chr(id, \(x)c(letters,rep(' ',6))[sample(1:32, 1000, rep=TRUE)] |> paste(collapse='')))

for(i in 1:nrow(dat)) jsonlite::write_json(as.list(dat[i,]), paste0(i, '.json'), auto_unbox=TRUE)

fl = list.files(pattern = '.json')

# test read
system.time({
  x = purrr::map_chr(fl, ~ jsonlite::fromJSON(.x)[['sentence']])
  data.frame(x=x) |> write.table(file = 'sentences-r.txt', row.names=FALSE, col.names=FALSE)
})

.. 报告以下测试读取阶段的运行时间:

   user  system elapsed 
  0.042   0.030   0.557 

正在 bash 中使用 jq 进行测试:

cat /dev/null > sentences-jq.txt
SECONDS=0

for file in $(ls *.json)./; do
    sentence=$(jq ".sentence" $file)
    echo $sentence >> sentences-jq.txt
done

echo runtime ${SECONDS}s

.. 报告运行时间为 5 秒。以慢着称的 R 却快了 10 倍。为了排除 >> 操作说明,我得到了与 echo $sentence > /dev/null.

相同的运行时间

我是否遗漏了一些有关 jq 工作原理的信息?

Am I missing something about how jq works?

jq 对 JSON 的处理通常非常快 (*),但每次调用 jq 确实有 non-trivial start-up 成本,因此您的 bash" " 循环是次优的,因为除非 glob 扩展失败,否则单个 jq 调用就足以产生相同的结果:

jq .sentence *.json

如果您对此有困难,那么既然您已经指出 ls *.json 有效,那么以下内容也应该有效:

cat *.json | jq .sentence

(*) 至少如果必须阅读整个 JSON 文档。为了比较,这里有一些读取 well-known JSON 文件的时间,jeopardy.json (**):

$ time jq length jeopardy.json
216930

user 0m1.266s
sys  0m0.271s

$ time gojq length jeopardy.json
216930

user 0m0.989s
sys  0m0.162s

$ time jaq length < jeopardy.json
216930

user 0m1.406s
sys  0m0.221s

$ time R -f <(echo 'require(jsonlite);x=read_json("jeopardy.json")' )
user 0m2.709s
sys  0m0.463s

$ time python3 <<< "import json; f = open('jeopardy.json'); data=json.load(f); print(len(data))"
216930

user 0m0.693s
sys  0m0.250s

time jj '#' < jeopardy.json 
216930

user 0m0.242s
sys  0m0.102s

对于某些类型的查询,替代 jq 的标准解析器会更快。一个可能相关的替代方案是 jq 自己的“流式解析器”,尽管它通常对 JSON 太大而无法放入内存的文本感兴趣。

(**) 通常命名为 JEOPARDY_QUESTIONS1.json 例如在 https://github.com/ryanwholey/jeopardy_bot/blob/master/JEOPARDY_QUESTIONS1.json

运行 jq(或任何程序)重复调用会耗费很长时间,因此我们需要尽可能减少调用次数。

对于 argument list too long errors,试试这个:

find . -maxdepth 1 -mindepth 1 -name "*.json" -exec jq .sentence {} +

find 尝试在对 jq.

的单次调用中附加尽可能多的 json 个文件