Benchmarking Erlang 27.0's new JSON module
2024-04-21
Here are a few benchmarks comparing Elixir's Jason
with the new :json
module, which will ship in the standard library with Erlang 27.0. The usual caveats apply: I chose these example documents, this is my own hardware, etc.
Always run these kind of benchmarks on your own hardware with your own workload.
inputs
$ ls -lah fixtures
Permissions Size User Date Modified Name
.rw-r--r-- 923k clark 21 Apr 13:23 big.json
.rw-r--r-- 3.9k clark 21 Apr 13:21 github.json
decode results
at [ 14:06:31 ] ➜ mix run decode_bench.exs
Operating System: macOS
CPU Information: Apple M1 Max
Number of Available Cores: 10
Available memory: 64 GB
Elixir 1.16.2
Erlang 27.0-rc3
JIT enabled: true
Benchmark suite executing with the following configuration:
warmup: 3 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: big, github
Estimated total run time: 32 s
Benchmarking :json.decode/1 with input big ...
Benchmarking :json.decode/1 with input github ...
Benchmarking Jason.decode/1 with input big ...
Benchmarking Jason.decode/1 with input github ...
Calculating statistics...
Formatting results...
##### With input big #####
Name ips average deviation median 99th %
:json.decode/1 184.17 5.43 ms ±1.19% 5.42 ms 5.77 ms
Jason.decode/1 170.45 5.87 ms ±7.24% 5.72 ms 6.65 ms
Comparison:
:json.decode/1 184.17
Jason.decode/1 170.45 - 1.08x slower +0.44 ms
##### With input github #####
Name ips average deviation median 99th %
:json.decode/1 83.18 K 12.02 μs ±18.69% 11.88 μs 14.54 μs
Jason.decode/1 61.64 K 16.22 μs ±25.01% 16 μs 19.17 μs
Comparison:
:json.decode/1 83.18 K
Jason.decode/1 61.64 K - 1.35x slower +4.20 μs
decode source
~c"27" = :erlang.system_info(:otp_release)
github = File.read!("fixtures/github.json")
big = File.read!("fixtures/big.json")
# just assert that these actually work
{:ok, _} = Jason.decode(github)
{:ok, _} = Jason.decode(big)
# these throw, so no match
:json.decode(github)
:json.decode(big)
Benchee.run(
%{
"Jason.decode/1" => fn input -> Jason.decode(input) end,
":json.decode/1" => fn input -> :json.decode(input) end
},
inputs: %{
"github" => github,
"big" => big
},
warmup: 3
)
encode results
$ mix run encode_bench.exs
Operating System: macOS
CPU Information: Apple M1 Max
Number of Available Cores: 10
Available memory: 64 GB
Elixir 1.16.2
Erlang 27.0-rc3
JIT enabled: true
Benchmark suite executing with the following configuration:
warmup: 3 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: big, github
Estimated total run time: 32 s
Benchmarking :json.encode/1 with input big ...
Benchmarking :json.encode/1 with input github ...
Benchmarking Jason.encode_to_iodata/1 with input big ...
Benchmarking Jason.encode_to_iodata/1 with input github ...
Calculating statistics...
Formatting results...
##### With input big #####
Name ips average deviation median 99th %
:json.encode/1 439.97 2.27 ms ±27.74% 2.09 ms 3.30 ms
Jason.encode_to_iodata/1 310.08 3.22 ms ±4.10% 3.29 ms 3.55 ms
Comparison:
:json.encode/1 439.97
Jason.encode_to_iodata/1 310.08 - 1.42x slower +0.95 ms
##### With input github #####
Name ips average deviation median 99th %
:json.encode/1 246.56 K 4.06 μs ±193.56% 3.96 μs 5.63 μs
Jason.encode_to_iodata/1 116.10 K 8.61 μs ±26.61% 8.38 μs 11.17 μs
Comparison:
:json.encode/1 246.56 K
Jason.encode_to_iodata/1 116.10 K - 2.12x slower +4.56 μs
encode source
~c"27" = :erlang.system_info(:otp_release)
github = File.read!("fixtures/github.json")
big = File.read!("fixtures/big.json")
# just assert that these actually work
{:ok, github_term} = Jason.decode(github)
{:ok, big_term} = Jason.decode(big)
# these throw, so no match
:json.decode(github)
:json.decode(big)
{:ok, _} = Jason.encode_to_iodata(github_term)
{:ok, _} = Jason.encode_to_iodata(big_term)
:json.encode(github_term)
:json.encode(big_term)
Benchee.run(
%{
"Jason.encode_to_iodata/1" => fn input -> Jason.encode_to_iodata(input) end,
":json.encode/1" => fn input -> :json.encode(input) end
},
inputs: %{
"github" => github_term,
"big" => big_term
},
warmup: 3
)
conclusion
It looks like there are some respectable gains to be had here, especially for encoding.
This :json
module looks like a great addition, and it will be really nice to have it in the standard library.
I wish it didn't handle errors with exceptions, and instead offered an {:ok, _} | {:error, _}
API, but we can probably graft that on after the fact in Elixir without too much trouble.
I've seen talk that :json
will be integrated into Jason
version 2 at some point,
so it's possible you won't have to change anything to realize these gains if you already use Jason
.