LoginSignup
39
27

More than 5 years have passed since last update.

RailsデフォルトのJSONレンダリングは遅いので注意。Ruby/RailsでJSON生成時に最もパフォーマンスが良い方法を調べてみた。

Last updated at Posted at 2019-01-20

RubyやRailsを使ってJSON生成する際にパフォーマンスを追求する機会があり、折角の機会なのでベンチマークを取って、最もパフォーマンスに優れた方法を調べてみました。

今回比較してみたのは以下の方法です。

1. ActiveSupport::JSON.encode
2. Yajl::Encoder.encode
3. JSON.generate
4. Oj.dump

それぞれ以下のような配列をJSON化することにしました。

def build_data(index)
  {
    id: index,
    author: "夏目漱石",
    product: "吾輩は猫である",
    description: "吾輩わがはいは猫である。名前はまだ無い。どこで生れたかとんと見当けんとうがつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪どうあくな種族であったそうだ。この書生というのは時々我々を捕つかまえて煮にて食うという話である。しかしその当時は何という考もなかったから別段恐しいとも思わなかった。",
    published_at: Date.new(1905, 10, 6)
  }
end

data = 5000.times.map { |i| build_data(i + 1) }

ハッシュ値を持った要素5000個を配列に持ち、これをそれぞれ50回JSONエンコードした時のパフォーマンスを計測しています。

Benchmark.bm do |x|
  x.report do
    50.times { ActiveSupport::JSON.encode(data) }
  end

  x.report do
    50.times { Oj.dump(data) }
  end

  x.report do
    50.times { JSON.generate(data) }
  end

  x.report do
    50.times { Yajl::Encoder.encode(data) }
  end

結果はOj > JSON > YAJL >>>> ActiveSupport

計測結果は次のようになりました。

type user system total real
ActiveSupport 19.450000 2.160000 21.610000 ( 24.657747)
YAJIL 5.070000 0.080000 5.150000 ( 5.192346)
JSON 2.440000 0.010000 2.450000 ( 2.482170)
Oj 1.560000 0.110000 1.670000 ( 1.724278)

あくまで一例ではありますが、今回の検証では「Oj > JSON > YAJL >>>> ActiveSupport」という順にパフォーマンスが高いことが分かりました。

Ojは「Optimized JSON」の略でパフォーマンス最適化されているのでイメージ通りですが、YAJLについてJSONモジュールより高速と謳われているにも関わらず、倍以上遅い結果に終わっているのが意外でした。

他の事例を調べてみたところ、データ量が多いとYAJLのほうが高速になることがあるものの、少量のデータでJSONのほうが早いケースもあり、「YAJLが早いらしい」という伝聞で使っているとかえってパフォーマンスが落ちる可能性があるので注意が必要です。

今回最も早かったOjですが、メモリ消費量もJSONよりも少なく、データ量が増えてもYAJLよりも依然として高速なことから、基本的にはOjを採用するというのが無難かつ効果的と言えるでしょう。

Railsでrender json: ~をデフォルトのまま使うのには注意

RailaからJSONを返すレスポンスにおいて、render json:@userというように、エンコードされていない配列やオブジェクトをそのままrenderしてしまいがちです。

この時@user.to_jsonされた結果が自動的で返されるのですが、Railsではto_jsonメソッドがJSONモジュールではなく、ActiveSupportのJSONモジュールが使われるように設定されています。(厳密には特定の場合のみデフォルトのJSONモジュールが使用される)

ActiveSupport::JSONのパフォーマンスについては先ほど示したように、他と比べて大きくパフォーマンスが落ちるため、高いスループットを実現するにはActiveSupportを使うのを避けるのが懸命でしょう。

最も望ましいのはOj.dumpを使ってあらかじめエンコードされたものを返すことです。

シンプルなオブジェクトならHashからエンコードしよう

RailsでAPIサーバーを作る時、パフォーマンスで最もボトルネックになるのはJSONの生成箇所です。

jbuilderはとても柔軟にJSONを生成できますが、もともとの性能に加えて、partialを使用すればするほどパフォーマンスはますます劣化します。

ActiveModel::Serializerを使えばいくらか向上しますが、それでもハッシュ値をエンコードするよりも大分パフォーマンスが落ちます。

jbuilderやActiveModel::Serializerは複雑なJSONを直感的に生成できる点で優れていますが、シンプルなオブジェクトをそのままJSON化するのであれば、素直にOjを使うだけで大きなパフォーマンスアップにつながります。

Ojを使うよりもJSONの方が早いケースもある

OjでActiveSupport::TimeWithZoneのエンコードをオプション無しで行うと、フォーマットがおかしなことになるので、compatモードで動かさざるを得ないことがあります。

公式サイトのベンチマークではJSONモジュールより早いと謳っていますが、手元で実際に動かしてみたところ「Oj通常モード > JSON > Ojのcompatモード」という測定結果になりました。少なくとも必ずしもOjの方が早いというわけでもなさそうです。

多くのケースでOjが早いことには変わりはありませんが、場合によってはJSONのほうが早いこともあり、特にパーサーを変えずにそのまま使っても決して悪い選択ではないと言えるでしょう。

39
27
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
27