つくりました。 rubygems.org
RapidJSON を使った Ruby 最強(当社調べ)の JSON パーサ&ジェネレータ。
使い方は GitHub もしくは ruby-doc を参照のこと。
もくじ
モチベーション
ruby の JSON パーサはかなり遅い。とくに JSON が巨大になるとしんどい。
この原因は 2 つある。
- そもそも JSON のパースが遅い
- Ruby のオブジェクトを作るのが遅い
これを両方解決しようというのがこの usamin。
JSON パースの高速化
JSON の高速なパーサとして RapidJSON が挙げられる。
python には python-rapidjson というラッパーがあるが、Ruby にはなかった。
RapidJSON を使うことで爆速パースが可能になった。
Ruby にある既存の JSON パーサとして oj が挙げられるが、こいつよりも速い。
特に数値に関しては Rational を使わないので、Ruby にもともと入っている json パッケージと同等の処理ができる。
なお JSON の生成も RapidJSON を通じて行えるようになった。
ちなみに整形済みのデータ生成(pretty_generate)は oj とかより若干遅い。
Ruby オブジェクト生成の遅延化
Twitter API などの巨大な JSON を扱う場合、そのすべてのデータが必要なわけではなく、普通は全体のデータの一部しか使わなくてよいことが多い。
しかし json や oj などのパッケージでは(当然ではあるが)Ruby オブジェクトに一度展開するため、Ruby のオブジェクトを作るのにそれなりの時間がかかってしまう。
usamin ではこれを解決するため、RapidJSON のデータを保持しておき、Hash や Array などにアクセスがなされたタイミングで Ruby のオブジェクトを生成する。
このため Hash、Array、String などのしんどいオブジェクトを Ruby 側で確保する回数を減らすことができる。
usamin ではこれを実現するため、Ruby の Hash や Array と同様にオブジェクトを扱えるようなクラスを Usamin::Value という形で独自実装している。
そのため Ruby の Hash や Array ではないにもかかわらず、インデックスアクセスはもちろん、each や map などの関数を実行することができる。
マニアックな話
Key-Value のアクセス
Ruby におけるいわゆる Hash と異なり、Key-Value ペアのアクセスは(Key からアクセスする場合)線形時間かかる。
あまりにも大きなオブジェクトを何度もアクセスする場合は、一度 eval することで 1 階層分の Hash を生成することで解決できる(どちらが速いかは微妙ではあるが)。
巨大な整数
RapidJSON 経由で数値をパースするため、巨大な整数をパースしようとすると double になって精度が落ちてしまう。
これは usamin 最大の欠点である。
なお生成時には、巨大な数値に対して直接書き出す手法を取るため、そのまま数値を出力することができる。
浮動小数点数の出力
RapidJSON の仕様で、微妙に json や oj と異なる出力がされる場合がある。
精度的には問題ないはず。
GC
usamin では GC が動いたタイミングで JSON ドキュメントを破棄する。
一方で、ドキュメントの一部のみ変数として残っていた場合、このドキュメントを破棄するとマズい。
そこでその変数が mark されたときに 1 つ上の部分を再帰的に mark することでこれを解決している。
ただしこの実装により、ほんの一部のデータを確保しているだけでドキュメント全体のオブジェクトが残ることになるので、メモリ効率が(場合によっては)若干悪い。
さいごに
ウサミン、シンデレラガールおめでとう。