pockestrap

Programmer's memo

StrongJSON の高速化実装、MightyJSON を作っている話

soutaro さんが作っている StrongJSON をより高速に実行する MightyJSON を作っています。

github.com

github.com

StrongJSON とは

StrongJSON とは、JSON の型チェックを行う便利Gemです。

# スキーマ定義
s = StrongJSON.new do
  let :item, object(id: number, name: string, count: number)
  let :customer, object(name: string, address: string, phone: string, email: optional(string))
  let :order, object(customer: customer, items: array(item))
end

json = JSON.parse(input, symbolize_names: true)

# json の値が定義したスキーマに合致すれば、それが order 変数に代入される。
# 合致しなければ例外が投げられる
order = s.order.coerce(json)

# === が定義されているので、case-when に渡すことも可能
case json
when s.item
  # json が item であればここが実行される
when s.customer
  # json が customer であればここが実行される
end

このような便利Gemなのですが、社内でsoutaroさんが「StrongJSONがボトルネックになっているかもしれない」ということを呟いているのを小耳に挟みました。 そのボトルネックは別の方法で解消されたようですが、面白そうなので高速化をしてみることにしました。

ベンチマーク

実際にどのくらい高速化出来たのかを示します。

縦軸は実行時間(秒)。 テストケースは https://github.com/pocke/mighty_json/tree/master/benchmarks に存在します。 また、各テストケースの実行回数はまちまちです。 例えば、Large-dataは20回しか実行していませんが(その代わり1回が大きい)、objectは2000000回実行しています。

f:id:Pocke:20170526115517p:plain

Large-data は実用的なデータセットですが、この場合2.8倍程度高速になっています。 またどのケースでも MightyJSON の方が数倍速くなっているのがわかると思います。

なお、MightyJSON の master では更に高速化がなされていますが、ベンチマークをし直すのがめんどくさかったので少し古いベンチマークとなっています。

実装方法

MightyJSON では、高速化の為に「スキーマ定義時にRubyコードを生成する」という戦略を取っています。
これによりより最適なコードを実行できること、またメソッド呼び出しの回数を減らすことが可能です。

これは todesking/patm と同じ戦略です。

そのた

  • MightyJSON は基本的に StrongJSON との互換性がありますが、いくつかの非互換があります。READMEをご覧ください。
  • 多分概ね動くと思いますが、MightyJSON のプロダクションへの投入はあまりオススメしません。人柱は歓迎です。StrongJSON をプロダクションで使うのは正解だと思います
  • コードを動的に生成しているため、デバッグには多少のエスパーを必要とします。