diff --git a/.gitignore b/.gitignore index 5f72a4a..c5974c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +# tools +.tool-versions + +# Intellij +/.idea +jason.iml + # The directory Mix will write compiled artifacts to. /_build diff --git a/bench/decode.exs b/bench/decode.exs deleted file mode 100644 index 553e1cb..0000000 --- a/bench/decode.exs +++ /dev/null @@ -1,59 +0,0 @@ -decode_jobs = %{ - "Jason" => fn {json, _} -> Jason.decode!(json) end, - "Poison" => fn {json, _} -> Poison.decode!(json) end, - "JSX" => fn {json, _} -> JSX.decode!(json, [:strict]) end, - "Tiny" => fn {json, _} -> Tiny.decode!(json) end, - "jsone" => fn {json, _} -> :jsone.decode(json) end, - "jiffy" => fn {json, _} -> :jiffy.decode(json, [:return_maps, :use_nil]) end, - "JSON" => fn {json, _} -> JSON.decode!(json) end, - # "binary_to_term/1" => fn {_, etf} -> :erlang.binary_to_term(etf) end, -} - -decode_inputs = [ - "GitHub", - "Giphy", - "GovTrack", - "Blockchain", - "Pokedex", - "JSON Generator", - "JSON Generator (Pretty)", - "UTF-8 escaped", - "UTF-8 unescaped", - "Issue 90", -] - -read_data = fn (name) -> - file = - name - |> String.downcase - |> String.replace(~r/([^\w]|-|_)+/, "-") - |> String.trim("-") - - json = File.read!(Path.expand("data/#{file}.json", __DIR__)) - etf = :erlang.term_to_binary(Jason.decode!(json)) - - {json, etf} -end - -inputs = for name <- decode_inputs, into: %{}, do: {name, read_data.(name)} - -IO.puts("Checking jobs don't crash") -for {name, input} <- inputs, {job, decode_job} <- decode_jobs do - IO.puts("Testing #{job} #{name}") - decode_job.(input) -end -IO.puts("\n") - -Benchee.run(decode_jobs, -# parallel: 4, - warmup: 5, - time: 30, - memory_time: 1, - inputs: inputs, - save: %{path: "output/runs/#{DateTime.utc_now()}.benchee"}, - load: "output/runs/*.benchee", - formatters: [ - {Benchee.Formatters.HTML, file: Path.expand("output/decode.html", __DIR__)}, - Benchee.Formatters.Console, - ] -) diff --git a/bench/encode.exs b/bench/encode.exs deleted file mode 100644 index dfaefd3..0000000 --- a/bench/encode.exs +++ /dev/null @@ -1,53 +0,0 @@ -encode_jobs = %{ - "Jason" => &Jason.encode_to_iodata!(&1, escape: :elixir_json), - "Jason native" => &Jason.encode_to_iodata!(&1, escape: :native_json), - # "Jason strict" => &Jason.encode_to_iodata!(&1, maps: :strict, escape: :elixir_json), - "Poison" => &Poison.encode!/1, - # "JSX" => &JSX.encode!/1, - # "Tiny" => &Tiny.encode!/1, - # "jsone" => &:jsone.encode/1, - "jiffy" => &:jiffy.encode/1, - # "JSON" => &JSON.encode!/1, - # "term_to_binary" => &:erlang.term_to_binary/1, -} - -encode_inputs = [ - "GitHub", - "Giphy", - "GovTrack", - "Blockchain", - "Pokedex", - "JSON Generator", - "UTF-8 unescaped", - "Issue 90", - "Canada", -] - -read_data = fn (name) -> - name - |> String.downcase - |> String.replace(~r/([^\w]|-|_)+/, "-") - |> String.trim("-") - |> (&"data/#{&1}.json").() - |> Path.expand(__DIR__) - |> File.read! -end - - -Benchee.run(encode_jobs, -# parallel: 4, - warmup: 2, - time: 15, - memory_time: 0.01, - reduction_time: 0.01, - inputs: for name <- encode_inputs, into: %{} do - name - |> read_data.() - |> Jason.decode!() - |> (&{name, &1}).() - end, - formatters: [ - {Benchee.Formatters.HTML, file: Path.expand("output/encode.html", __DIR__)}, - Benchee.Formatters.Console - ] -) diff --git a/bench/lib/tasks/bench/decode.ex b/bench/lib/tasks/bench/decode.ex new file mode 100644 index 0000000..892af10 --- /dev/null +++ b/bench/lib/tasks/bench/decode.ex @@ -0,0 +1,128 @@ +defmodule Mix.Tasks.Bench.Decode do + use Mix.Task + @default_jobs [ + "Jason", + "Poison", + "JSX", + "Tiny", + "jsone", + "jiffy", + "JSON" + ] + + @default_inputs [ + "GitHub", + "Giphy", + "GovTrack", + "Blockchain", + "Pokedex", + "JSON Generator", + "JSON Generator (Pretty)", + "UTF-8 escaped", + "UTF-8 unescaped", + "Issue 90", + ] + + + defp extract_values(args, key, default \\ []) do + Enum.map( + args, + fn arg -> + case arg do + {^key, input} -> input + _ -> nil + end + end + ) + |> Enum.reject(&is_nil/1) + |> case do + [] -> default + x -> x + end + end + + defp jobs(args) do + extract_values(args, :job, @default_jobs) + |> Enum.map( + fn(job) -> + case job do + "Jason" -> {job, fn {json, _} -> Jason.decode!(json) end} + "Poison" -> {job, fn {json, _} -> Poison.decode!(json) end} + "JSX" -> {job, fn {json, _} -> JSX.decode!(json, [:strict]) end} + "Tiny" -> {job, fn {json, _} -> Tiny.decode!(json) end} + "jsone" -> {job, fn {json, _} -> :jsone.decode(json) end} + "jiffy" -> {job, fn {json, _} -> :jiffy.decode(json, [:return_maps, :use_nil]) end} + "JSON" -> {job, fn {json, _} -> JSON.decode!(json) end} + "binary_to_term/1" -> {job, fn {_, etf} -> :erlang.binary_to_term(etf) end} + end + end + ) |> Map.new() + end + + defp inputs(args) do + extract_values(args, :input, @default_inputs) + end + + defp read_data(name) do + file = + name + |> String.downcase + |> String.replace(~r/([^\w]|-|_)+/, "-") + |> String.trim("-") + + json = File.read!(Path.expand("../../../data/#{file}.json", __DIR__)) + etf = :erlang.term_to_binary(Jason.decode!(json)) + + {json, etf} + end + + defp benchee(args) do + inputs = for name <- inputs(args), into: %{}, do: {name, read_data(name)} + jobs = jobs(args) + + if args[:test] == nil or args[:text] == true do + IO.puts("Checking jobs don't crash") + for {name, input} <- inputs, {job, decode_job} <- jobs do + IO.puts("Testing #{job} #{name}") + decode_job.(input) + end + IO.puts("\n") + end + + warmup = args[:warmup] || 5 + time = args[:time] || 30 + memory_time = args[:memory_time] || 1 + + Benchee.run(jobs, + # parallel: 4, + warmup: warmup, + time: time, + memory_time: memory_time, + inputs: inputs, + save: %{path: "../../../output/runs/#{DateTime.utc_now()}.benchee"}, + load: "../../../output/runs/*.benchee", + formatters: [ + {Benchee.Formatters.HTML, file: Path.expand("../../../output/decode.html", __DIR__)}, + Benchee.Formatters.Console, + ] + ) + end + + + def run(argv) do + {args, _, _} = OptionParser.parse( + argv, + strict: [ + test: :boolean, + input: [:string,:keep], + job: [:string,:keep], + warmup: :integer, + time: :integer, + memory_time: :float, + reduction_time: :float, + ] + ) + benchee(args) + end + +end diff --git a/bench/lib/tasks/bench/encode.ex b/bench/lib/tasks/bench/encode.ex new file mode 100644 index 0000000..6647215 --- /dev/null +++ b/bench/lib/tasks/bench/encode.ex @@ -0,0 +1,118 @@ +defmodule Mix.Tasks.Bench.Encode do + use Mix.Task + + @default_jobs [ + "Jason", + "Jason native", + "Poison", + "jiffy", + ] + + @default_inputs [ + "GitHub", + "Giphy", + "GovTrack", + "Blockchain", + "Pokedex", + "JSON Generator", + "UTF-8 unescaped", + "Issue 90", + "Canada", + ] + + + defp extract_values(args, key, default \\ []) do + Enum.map( + args, + fn arg -> + case arg do + {^key, input} -> input + _ -> nil + end + end + ) + |> Enum.reject(&is_nil/1) + |> case do + [] -> default + x -> x + end + end + + defp jobs(args) do + extract_values(args, :job, @default_jobs) + |> Enum.map( + fn(job) -> + case job do + "Jason" -> {job, &Jason.encode_to_iodata!(&1, escape: :elixir_json)} + "Jason native" -> {job, &Jason.encode_to_iodata!(&1, escape: :native_json)} + "Jason strict" -> {job, &Jason.encode_to_iodata!(&1, maps: :strict, escape: :elixir_json)} + "Poison" -> {job, &Poison.encode!/1} + "JSX" -> {job, &JSX.encode!/1} + "Tiny" -> {job, &Tiny.encode!/1} + "jsone" -> {job, &:jsone.encode/1} + "jiffy" -> {job, &:jiffy.encode/1} + "JSON" -> {job, &JSON.encode!/1} + "term_to_binary" -> {job, &:erlang.term_to_binary/1} + end + end + ) |> Map.new() + end + + defp inputs(args) do + extract_values(args, :input, @default_inputs) + end + + defp read_data(name) do + name + |> String.downcase + |> String.replace(~r/([^\w]|-|_)+/, "-") + |> String.trim("-") + |> (&"../../../data/#{&1}.json").() + |> Path.expand(__DIR__) + |> File.read! + end + + defp benchee(args \\ nil) + defp benchee(args) do + warmup = args[:warmup] || 2 + time = args[:time] || 15 + memory_time = args[:memory_time] || 0.01 + reduction_time = args[:reduction_time] || 0.01 + + encode_jobs = jobs(args) + encode_inputs = inputs(args) + Benchee.run(encode_jobs, + # parallel: 4, + warmup: warmup, + time: time, + memory_time: memory_time, + reduction_time: reduction_time, + inputs: for name <- encode_inputs do + name + |> read_data() + |> Jason.decode!() + |> (&{name, &1}).() + end, + formatters: [ + {Benchee.Formatters.HTML, file: Path.expand("../../../output/encode.html", __DIR__)}, + Benchee.Formatters.Console + ] + ) + end + + def run(argv) do + {args, _, _} = OptionParser.parse( + argv, + strict: [ + input: [:string,:keep], + job: [:string,:keep], + warmup: :integer, + time: :integer, + memory_time: :float, + reduction_time: :float, + ] + ) + benchee(args) + end + +end diff --git a/bench/mix.exs b/bench/mix.exs index d4927da..7a9ea87 100644 --- a/bench/mix.exs +++ b/bench/mix.exs @@ -7,15 +7,7 @@ defmodule JasonBench.MixProject do version: "0.1.0", elixir: "~> 1.6", start_permanent: Mix.env() == :prod, - deps: deps(), - aliases: aliases() - ] - end - - defp aliases() do - [ - "bench.encode": ["run encode.exs"], - "bench.decode": ["run decode.exs"] + deps: deps() ] end