前記事に続いてmrubyネタ yyjsonという高速でC言語から扱えるJSONライブラリがある。
これをmrubyから使えるようにしたmruby-yyjsonを作った
きっかけ
rfではmruby-jsonを使っている。 mruby-jsonの処理速度に不満があったわけではないのだが、2つ問題点があった。 1つ目はJSONのObjectに重複するキーがあるとinvalid jsonとなってしまう。 これは結構分かりづらいエラーで、私がこの問題にぶつかったときにgdbでデバッグするというくらいまで悩んだ問題だった。。 しかし、この問題はCRubyのjsonライブラリやjqでは発生しないので、この問題を解決したいと考えていた。
$ echo '{"foo":1,"foo":2}' | rf -j _ Error: invalid json # CRubyではエラーにならない $ echo '{"foo":1,"foo":2}' | ruby -rjson -e 'pp JSON.parse(STDIN.read)' {"foo"=>2} # jqでもエラーにならない $ echo '{"foo":1,"foo":2}' | jq -c . {"foo":2}
2つ目はエラーがわかりづらいこと。 先のエラーがinvalid jsonしかエラーに出なくてすごく大変だったのは書いた通り。
これらを解決しようとmruby-jsonのコードを読んでいたのだが、JSONパーサ自体に手をいれる必要がありそうに感じた(たぶん)のと、折角なら自作するかっとなりmruby-yyjsonを作った。
使い方
使い方は簡単でbuild_config.rbに conf.gem github: 'buty4649/mruby-yyjson'
を書くだけ。
MRuby::Build.new do |conf| -- snip -- conf.gem github: 'buty4649/mruby-yyjson' end
おわりに
mruby-yyjsonを作った。 yyjsonのAPIは非常にシンプルで使いやすいので、mruby bindingもかなりすんなり作れてよかった。 一通りは作ったと思うのでご活用ください。
おまけ: ベンチマーク
yyjsonは速さをウリにしているのでmruby-jsonとの比較ベンチを取ってみた。 ベンチマークのデータセットは https://github.com/ibireme/yyjson_benchmark にあるcanada.jsonとfgo.json。 前者はJSONベンチマーク界でスタンダードなファイル(?)なのと、後者は巨大で日本語が混じっていて面白そうだった*1ので選定した。
canada.json
$ hyperfine --input canada.json --warmup 3 './build/mruby-yyjson/bin/mruby -e "JSON.parse(STDIN.read)"' './build/mruby-json/bin/mruby -e "JSON.parse(STDIN.read)"' Benchmark 1: ./build/mruby-yyjson/bin/mruby -e "JSON.parse(STDIN.read)" Time (mean ± σ): 11.7 ms ± 1.3 ms [User: 6.5 ms, System: 3.2 ms] Range (min … max): 9.2 ms … 16.2 ms 222 runs Benchmark 2: ./build/mruby-json/bin/mruby -e "JSON.parse(STDIN.read)" Time (mean ± σ): 36.9 ms ± 2.7 ms [User: 29.1 ms, System: 6.5 ms] Range (min … max): 33.6 ms … 45.0 ms 69 runs Summary ./build/mruby-yyjson/bin/mruby -e "JSON.parse(STDIN.read)" ran 3.15 ± 0.41 times faster than ./build/mruby-json/bin/mruby -e "JSON.parse(STDIN.read)"
fgo.json
$ hyperfine --input fgo.json --warmup 3 './build/mruby-yyjson/bin/mruby -e "JSON.parse(STDIN.read)"' './build/mruby-json/bin/mruby -e "JSON.parse(STDIN.read)"' Benchmark 1: ./build/mruby-yyjson/bin/mruby -e "JSON.parse(STDIN.read)" Time (mean ± σ): 717.1 ms ± 16.3 ms [User: 633.5 ms, System: 120.3 ms] Range (min … max): 695.3 ms … 753.9 ms 10 runs Benchmark 2: ./build/mruby-json/bin/mruby -e "JSON.parse(STDIN.read)" Time (mean ± σ): 1.067 s ± 0.052 s [User: 0.969 s, System: 0.141 s] Range (min … max): 1.016 s … 1.183 s 10 runs Summary ./build/mruby-yyjson/bin/mruby -e "JSON.parse(STDIN.read)" ran 1.49 ± 0.08 times faster than ./build/mruby-json/bin/mruby -e "JSON.parse(STDIN.read)"
build_config.rb
MRuby::Build.new('mruby-yyjson') do |conf| conf.toolchain conf.cc.flags << '-O3' conf.gembox 'default' conf.gem File.expand_path( __dir__) end MRuby::Build.new('mruby-json') do |conf| conf.toolchain conf.cc.flags << '-O3' conf.gembox 'default' conf.gem mgem: 'mruby-json' end
*1:Fate/Grand OrderのJSONらしいが公式のデータなのかはたまた…