Повышение производительности при использовании jq для обработки больших файлов - PullRequest
3 голосов
/ 10 июля 2020

Вариант использования

Мне нужно разбить большие файлы (~5G) данных JSON на файлы меньшего размера с разделителями новой строки JSON с эффективным использованием памяти (т.е. , без необходимости читать весь JSON blob в память). Данные 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, как описано в этом сообщении SO . Такой подход эффективен с точки зрения памяти благодаря потоковому парсеру 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 по сравнению с 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

Вопросы

  1. Ожидается ли такая более низкая скорость обработки для jq по сравнению с sed? Имеет смысл, что jq будет медленнее, учитывая, что он выполняет много проверок под капотом, но в 4 раза медленнее не кажется правильным.
  2. Могу ли я что-нибудь сделать, чтобы улучшить скорость, с которой jq может обработать этот файл? Я бы предпочел использовать jq для обработки файлов, потому что я уверен, что он может беспрепятственно обрабатывать другие форматы вывода строк, но, учитывая, что я обрабатываю тысячи файлов каждый день, трудно оправдать наблюдаемую мной разницу в скорости.
...