Optimizing JSON performance in Rails
Scraping search results at SerpApi often involves JSON parsing. SerpApi is also generating large JSON responses, where JSON serialization takes place. It is worthwhile to investigate if we can speed up JSON processing.
A quick search of the Ruby ecosystem shows Oj as the best fit for Rails. It has support for being a drop-in replacement with super easy configuration. We benchmarked Oj
vs. the standard json
library that ships with Ruby.
Since json
performance may differ across Ruby versions, we ran the same benchmark code under Ruby 2.7.2, Ruby 3.1.2, and Ruby 3.1.2 +YJIT.
We compared four methods that are mainly used: JSON.parse
, obj.to_json
, JSON.dump
, JSON.pretty_generate
. We ran each method for 10K iterations and compared the time taken.
Here is the benchmark code:
def run(result)
json = File.read('test.json')
result[:parse] << Benchmark.ms {
10000.times do
JSON.parse(json)
end
}
puts "parse #{result[:parse].last}"
hash = JSON.parse(json)
result[:to_json] << Benchmark.ms {
10000.times do
hash.to_json
end
}
puts "to_json #{result[:to_json].last}"
hash = JSON.parse(json)
result[:dump] << Benchmark.ms {
10000.times do
JSON.dump(hash)
end
}
puts "dump #{result[:dump].last}"
hash = JSON.parse(json)
result[:pretty_generate] << Benchmark.ms {
10000.times do
JSON.pretty_generate(hash)
end
}
puts "pretty_generate #{result[:pretty_generate].last}"
end
json_result = {
parse: [],
to_json: [],
dump: [],
pretty_generate: []
}
10.times { run(json_result) }
Oj.optimize_rails()
oj_result = {
parse: [],
to_json: [],
dump: [],
pretty_generate: []
}
10.times { run(oj_result) }
[json_result, oj_result].each do |result|
result.each do |k, v|
result[k] = v.sum / v.size
end
end
pp json_result
pp oj_result
We ran the tests on DigitalOcean CPU-Optimized, 2 vCPUs, 4 GB
.
Here are the results:
And here is the raw data:
Ruby 2.7.2
json | Oj | |
---|---|---|
JSON.parse | 7994.661393 | 4043.134571 |
obj.to_json | 64983.64482 | 3405.177611 |
JSON.dump | 2713.536324 | 1633.386881 |
JSON.pretty_generate | 2964.274016 | 1938.549645 |
Ruby 3.1.2
json | Oj | |
---|---|---|
JSON.parse | 6005.806509 | 4317.333413 |
obj.to_json | 72626.32646 | 2760.772417 |
JSON.dump | 2901.954273 | 1708.129035 |
JSON.pretty_generate | 3214.653918 | 2136.254046 |
Ruby 3.1.2 +YJIT
json | Oj | |
---|---|---|
JSON.parse | 5963.452377 | 4363.962555 |
obj.to_json | 69703.34656 | 2790.86789 |
JSON.dump | 2873.292326 | 1712.871591 |
JSON.pretty_generate | 3158.897077 | 2179.701062 |
We have also checked if the Oj
and json
outputs are identical.
Oj
outperforms json
on all tested ruby versions.
On Ruby 2.7.2, Oj
has 1.97x JSON.parse
, 19x obj.to_json
, 1.69x JSON.dump
, 1.5x JSON.pretty_generate
. We can easily improve JSON performance by at least 50% with a few lines of code!
Ruby 3.1.2 has improved JSON.parse
, but other methods have become slower. Ruby 3.1.2 with YJIT enabled does not have a significant impact on performance.
obj.to_json
is unexpectedly slow by default. According to the documentation, Oj
has optimized the behavior. We have yet to check if the output will differ on model objects, but we can certainly use it on pure Hash
without a problem.
That's all for the JSON benchmark. Thank you for reading! See you at the following benchmark post.