はじめ
ウェブサービスをやっていく中にはAPIを取得することが多い。今みているmastodon
ではOJ
というJson parser
を使っていたので調べてみた。
インストール
or
1
2
| # in Gemfile
gem 'oj'
|
使い方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| require 'oj'
h = { 'one' => 1, 'array' => [ true, false ] }
json = Oj.dump(h)
# json =
# {
# "one":1,
# "array":[
# true,
# false
# ]
# }
h2 = Oj.load(json)
puts "Same? #{h == h2}"
# true
|
他のParserとの比較
まずロカールにapiのコントローラーをよいする。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| class Api::JsonCompareController < ActionController::API
def index
json = {
test1: 'test1',
test2: 'test2',
test3: 'test3',
test4: 'test4',
test5: 'test5',
test6: 'test6',
test7: 'test7',
test8: 'test8',
test9: 'test9',
test10: 'test10'
}
render json: json, status: :ok
end
end
|
それから比較するコードを書く。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| class JsonCompare
require 'uri'
require 'net/http'
require 'json'
require 'yajl'
require 'oj'
require 'benchmark/ips'
def compare
uri = URI "http://localhost:3000/api/json_compare"
response = Net::HTTP.get uri
Benchmark.ips do |x|
x.report("JSON.parse") { JSON.parse(response) }
x.report("Yajl") { Yajl::Parser.new.parse(response) }
x.report("Oj") { Oj.load(response) }
x.compare!
end
end
end
|
結果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| Warming up --------------------------------------
JSON.parse 9.786k i/100ms
Yajl 10.364k i/100ms
Oj 17.009k i/100ms
Calculating -------------------------------------
JSON.parse 99.053k (± 3.2%) i/s - 499.086k in 5.043910s
Yajl 107.150k (± 3.4%) i/s - 538.928k in 5.036016s
Oj 183.400k (± 3.0%) i/s - 918.486k in 5.012844s
Comparison:
Oj: 183399.8 i/s
Yajl: 107149.6 i/s - 1.71x slower
JSON.parse: 99053.1 i/s - 1.85x slower
=> #<Benchmark::IPS::Report:0x007fdef3b04440
@data=nil,
@entries=
[#<Benchmark::IPS::Report::Entry:0x007fdef8c3cbf0 @iterations=499086, @label="JSON.parse", @measurement_cycle=9786, @microseconds=5043910.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdef8c3cc68 @error=3164, @mean=99053.0983205518>>,
#<Benchmark::IPS::Report::Entry:0x007fdef7a234a0 @iterations=538928, @label="Yajl", @measurement_cycle=10364, @microseconds=5036016.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdef7a23568 @error=3672, @mean=107149.64088869188>>,
#<Benchmark::IPS::Report::Entry:0x007fdef89e6c98 @iterations=918486, @label="Oj", @measurement_cycle=17009, @microseconds=5012844.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdef89e6d10 @error=5584, @mean=183399.80361005384>>]>
|
OJ
がYajl
よりは1.7倍、JSON
よりは1.8倍速かった。
String
じゃなくてモデルにしたらどうなるだろう。
apiコントローラーのコードを変えてみる。
1
2
3
4
5
6
| class Api::JsonCompareController < ActionController::API
def index
@users = User.order(:id)
render json: @users, status: :ok
end
end
|
ちなみにUser.count
は100
。
さっきの比較コードを実行してみると、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| Warming up --------------------------------------
JSON.parse 253.000 i/100ms
Yajl 259.000 i/100ms
Oj 387.000 i/100ms
Calculating -------------------------------------
JSON.parse 2.529k (± 3.4%) i/s - 12.650k in 5.007160s
Yajl 2.612k (± 2.6%) i/s - 13.209k in 5.061015s
Oj 3.952k (± 3.6%) i/s - 20.124k in 5.098328s
Comparison:
Oj: 3952.4 i/s
Yajl: 2611.7 i/s - 1.51x slower
JSON.parse: 2529.5 i/s - 1.56x slower
=> #<Benchmark::IPS::Report:0x007fdf594c4678
@data=nil,
@entries=
[#<Benchmark::IPS::Report::Entry:0x007fdf5da291a0 @iterations=12650, @label="JSON.parse", @measurement_cycle=253, @microseconds=5007160.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdf5da29268 @error=87, @mean=2529.463823054468>>,
#<Benchmark::IPS::Report::Entry:0x007fdf5a59b8c0 @iterations=13209, @label="Yajl", @measurement_cycle=259, @microseconds=5061015.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdf5a59ba28 @error=67, @mean=2611.693591724044>>,
#<Benchmark::IPS::Report::Entry:0x007fdf5d821510 @iterations=20124, @label="Oj", @measurement_cycle=387, @microseconds=5098328.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdf5d821588 @error=141, @mean=3952.373849831794>>]>
|
OJ
がYajl
、JSON
より1.5倍速いことがわかる。
まとめ
単純に速度のパフォーマンス側をみるとOJ
を使った方が良さそう!
追記
mastodonでのOJの使い方
1
2
| # app/lib/emoji.rb
data = Oj.load(File.open(Rails.root.join('lib', 'assets', 'emoji.json')))
|
1
2
3
4
5
6
7
8
9
10
11
12
| # app/services/batched_remove_status_service.rb
@json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id)] }.to_h
# app/services/fan_out_on_write_service.rb
@payload = InlineRenderer.render(status, nil, :status)
@payload = Oj.dump(event: :update, payload: @payload)
# app/services/notify_service.rb
Redis.current.publish("timeline:#{@recipient.id}", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification)))
# app/services/remove_status_service.rb
@payload = Oj.dump(event: :delete, payload: status.id)
|
1
2
3
4
5
| // app/views/home/index.html.haml
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
// app/views/about/show.html.haml
#mastodon-timeline{ data: { props: Oj.dump(default_props) } }
|
参考
RubyのJSONパーサーのパース速度比較
OJ Github