Как стать автором
Обновить

Комментарии 16

Нода как runtime-среда для сервера? Спасибо, не надо. Т.е. я ценю попытку, и будь это более системный язык (C/C++/Rust/Go/perl/python/awk/bash), оно бы прокатило. Делать node системной ради одного query — нет, спасибо, не надо.

Возможно, я не вполне понятно описал, но ноды как рантайм-среды там нет ни в каком виде. Утилита представляет собой самодостаточный исполняемый файл, который имеет составной частью движок QuickJS для реализации логики DSL.
… компилятор qjsc джаваскрипта в исполняемый файл

Это не совсем компилятор, т.е. производительность всё равно будет в десятки раз меньше чем V8 после jit, для больших JSON (или много мелких) запросов не особо-то разгуляешься.


А в остальном тулза хорошая получилась, гораздо приятней (и понятней) чем jq.

Что стараетесь держать размер исполняемого файла небольшим это круто, моё почтение! Но как по мне потребление RAM и CPU важнее. Можете проделать какие-нибудь бенчмарки в сравнении с jshon и jq?

В общем сделал замеры сам. Сравниваю с jq 1.4 (потому что последующие релизы 1.5 и 1.6 гораздо медленнее) и jshon.

Тесты с маленьким файлом:

# cat sample2
[{"n": "bob", "v": 100}, {"n": "jim", "v": 101}]
# hyperfine 'jshon -a -e n < sample2' "jq_1.4/jq '.[].n' < sample2" "jsqry 'n' < sample2"
Benchmark 1: jshon -a -e n < sample2
  Time (mean ± σ):       0.4 ms ±   0.1 ms    [User: 0.4 ms, System: 0.1 ms]
  Range (min … max):     0.3 ms …   1.2 ms    1701 runs
 
Benchmark 2: jq_1.4/jq '.[].n' < sample2
  Time (mean ± σ):       0.9 ms ±   0.2 ms    [User: 0.8 ms, System: 0.1 ms]
  Range (min … max):     0.6 ms …   1.8 ms    1261 runs

Benchmark 3: jsqry 'n' < sample2
  Time (mean ± σ):       1.2 ms ±   0.2 ms    [User: 1.0 ms, System: 0.2 ms]
  Range (min … max):     1.0 ms …   2.4 ms    1312 runs

Summary
  'jshon -a -e n < sample2' ran
    2.04 ± 0.74 times faster than 'jq_1.4/jq '.[].n' < sample2'
    2.89 ± 0.93 times faster than 'jsqry 'n' < sample2'
    
# /usr/bin/time --format='%M maxresident KiB' jshon -a -e n < sample2 >/dev/null 
1568 maxresident KiB
# /usr/bin/time --format='%M maxresident KiB' jq_1.4/jq '.[].n' < sample2 >/dev/null 
2180 maxresident KiB
# /usr/bin/time --format='%M maxresident KiB' jsqry 'n' < sample2 >/dev/null 
2840 maxresident KiB

Тесты на большом файле:

#almost 2MB testfile parse speed
TESTFILE=/usr/lib/python3/dist-packages/botocore/data/ec2/2016-11-15/service-2.json hyperfine \
  'jshon -e operations -e WithdrawByoipCidr < $TESTFILE' \
  'jq_1.4/jq .operations.WithdrawByoipCidr < $TESTFILE' \
  'jsqry .operations.WithdrawByoipCidr < $TESTFILE'

Benchmark 1: jshon -e operations -e WithdrawByoipCidr < $TESTFILE
  Time (mean ± σ):      44.2 ms ±   2.8 ms    [User: 38.9 ms, System: 5.1 ms]
  Range (min … max):    41.3 ms …  62.0 ms    63 runs
 
Benchmark 2: jq_1.4/jq .operations.WithdrawByoipCidr < $TESTFILE
  Time (mean ± σ):      46.9 ms ±   3.0 ms    [User: 42.0 ms, System: 4.7 ms]
  Range (min … max):    43.7 ms …  58.6 ms    63 runs
 
Benchmark 3: jsqry .operations.WithdrawByoipCidr < $TESTFILE
  Time (mean ± σ):     990.5 ms ±  27.5 ms    [User: 949.9 ms, System: 37.1 ms]
  Range (min … max):   947.6 ms … 1046.4 ms    10 runs
 
Summary
  'jshon -e operations -e WithdrawByoipCidr < $TESTFILE' ran
    1.06 ± 0.09 times faster than 'jq_1.4/jq .operations.WithdrawByoipCidr < $TESTFILE'
   22.39 ± 1.54 times faster than 'jsqry .operations.WithdrawByoipCidr < $TESTFILE'
   
   
#almost 2MB testfile RAM usage
TESTFILE=/usr/lib/python3/dist-packages/botocore/data/ec2/2016-11-15/service-2.json
# /usr/bin/time --format='%M maxresident KiB' jshon -e operations -e WithdrawByoipCidr < $TESTFILE >/dev/null
10120 maxresident KiB
# /usr/bin/time --format='%M maxresident KiB' jq_1.4/jq .operations.WithdrawByoipCidr < $TESTFILE >/dev/null
8964 maxresident KiB
# /usr/bin/time --format='%M maxresident KiB' jsqry .operations.WithdrawByoipCidr < $TESTFILE >/dev/null
64684 maxresident KiB

Итого вердикт: для небольших JSON jsqry ведёт себя достойно, хоть и несколько прожорливее аналогов, а вот на больших данных использование CPU и RAM на порядок больше по сравнению с аналогами.

Было бы хорошо, если бы вы привели примеры использования jq и вашей утилиты на подобных вещах: "Отфильтровать элементы больше 2, добавить к каждому 100, отсортировать по убыванию и взять 2 последних элемента."


Чтобы предметно увидеть преимущества и простоту вашего DSL по сравнению с jq.


Недавно убирал DSL на базе jq из конфигурационных файлов некоторого сервиса, где с помощью него делалась фильтрация и мэппинг данных из одной структуры в другую. Это, возможно гибко и мощно, но абсолютно нечитаемо.

Конкретно этот пример будет так (похоже, но на jq длиннее)

$ echo '[1,2,3,4,5]' | jq 'map(select(.>2)) | map(.+100) | sort_by(-.) | .[-2:]'
[
104,
103
]

$ echo '[1,2,3,4,5]' | jsqry '[_>2] {_+100} s(-_) [-2:]'
[
104,
103
]

Тут больше сравнительных примеров: gist.github.com/xonixx/d6066e83ec0773df248141440b18e8e4

На мой взгляд у jq наркоманский синтаксис, но у вас получилось сделать ещё более наркоманский и менее понятный синтаксис

На самом деле синтаксис простой, если понять строительные блоки, коих немного. Это:


  1. field1.field2.field3 — доступ по полям
  2. [ expression ] — фильтрация
  3. { expression } — трансформация (map)
  4. [ from:to:step ] — срезы в стиле Python
  5. s( expression ) — сортировка

Где expression — выражение с "_" которое под капотом соответствует JS функции function(_) { return expression }. Например, фильтр [ _ > 5 ] соответствует предикату function(_) { return _ > 5 }. Все, этого знания достаточно чтоб написать 90% типовых запросов.

а сделайте пожалуйста pull request?
а на 64bit win не получается собрать?
Я пока не занимался правкой скриптов. Задача была проверить возможность сборки, поэтому я сделал это вручную. Для сборки использовался MinGW, поэтому, думаю, без проблем можно собрать и 64-битную версию. Главная проблема, собрать QuickJS, покуда в его Makefil'е присутствуют изъяны: например, строка «HOST_LIBS=-lm -ldl -lpthread» на Windows является лишней.
а какова все таки производительность?

Интересно, а jsonnet не рассматривали в качестве альтернативы?

Кстати, есть еще pwsh:
Write-Host '[1,2,3,4,5]' | ConvertFrom-Json -Depth 99 | Where-Object {$_ -gt 2} | ForEach-Object{$_+100} | Select-Object -Last 2

Или короче:
echo '[1,2,3,4,5]' | ConvertFrom-Json -Depth 99 | ?{$_ -gt 2} | %{$_+100} | select -Last 2


Замер скорости работы этого примера на случайном linux-сервере:
jq(time, real):~ 7мс
jsqry(time, real):~ 5мс
pwsh(Measure-Command):~ 4мс
(НО! Первый старт pwsh был ~50мс, последующие быстрее, даже если менять предикаты или кол-во входных элементов)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации