Skip to content

Instantly share code, notes, and snippets.

@asakura
Created June 16, 2023 19:35
Show Gist options
  • Select an option

  • Save asakura/6b3a1126496ffdfc1730803e8d57965f to your computer and use it in GitHub Desktop.

Select an option

Save asakura/6b3a1126496ffdfc1730803e8d57965f to your computer and use it in GitHub Desktop.

Benchmark map/struct access syntaxes

Mix.install([
  {:benchee_dsl, "~> 0.5"}
  # {:benchee_markdown, "~> 0.3"}
])

Section

defmodule UnderTest do
  defstruct [:a, :b]

  {:function, :unpack, 1, 26,
   [
     {:line, 12},
     {:label, 25},
     {:func_info, {:atom, Test}, {:atom, :unpack}, 1},
     {:label, 26},
     {:test, :is_map, {:f, 25}, [x: 0]},
     {:get_map_elements, {:f, 25},
      {:tr, {:x, 0}, {{:t_map, :any, :any}, 0, 18_446_744_073_709_551_615}},
      {:list, [atom: :b, x: 3, atom: :a, x: 2, atom: :__struct__, x: 1]}},
     {:test, :is_eq_exact, {:f, 25}, [x: 1, atom: Test]},
     {:line, 13},
     {:gc_bif, :+, {:f, 0}, 4, [x: 2, x: 3], {:x, 0}},
     :return
   ]}

  def unpack(%__MODULE__{a: a, b: b}) do
    a + b
  end

  {:function, :match, 1, 20,
   [
     {:line, 6},
     {:label, 19},
     {:func_info, {:atom, Test}, {:atom, :match}, 1},
     {:label, 20},
     {:test, :is_map, {:f, 19}, [x: 0]},
     {:get_map_elements, {:f, 19},
      {:tr, {:x, 0}, {{:t_map, :any, :any}, 0, 18_446_744_073_709_551_615}},
      {:list, [atom: :__struct__, x: 1]}},
     {:test, :is_eq_exact, {:f, 19}, [x: 1, atom: Test]},
     {:get_map_elements, {:f, 21},
      {:tr, {:x, 0}, {{:t_map, :any, :any}, 0, 18_446_744_073_709_551_615}},
      {:list, [atom: :b, x: 2, atom: :a, x: 1]}},
     {:line, 7},
     {:gc_bif, :+, {:f, 0}, 3, [x: 1, x: 2], {:x, 0}},
     :return,
     {:label, 21},
     {:line, 8},
     {:badmatch, {:x, 0}}
   ]}

  def match(%__MODULE__{} = struct) do
    %__MODULE__{a: a, b: b} = struct
    a + b
  end

  {:function, :dottet_access, 1, 15,
   [
     {:line, 2},
     {:label, 14},
     {:func_info, {:atom, Test}, {:atom, :dottet_access}, 1},
     {:label, 15},
     {:test, :is_map, {:f, 14}, [x: 0]},
     {:get_map_elements, {:f, 14},
      {:tr, {:x, 0}, {{:t_map, :any, :any}, 0, 18_446_744_073_709_551_615}},
      {:list, [atom: :__struct__, x: 1]}},
     {:test, :is_eq_exact, {:f, 14}, [x: 1, atom: Test]},
     {:get_map_elements, {:f, 17},
      {:tr, {:x, 0}, {{:t_map, :any, :any}, 0, 18_446_744_073_709_551_615}},
      {:list, [atom: :a, x: 1]}},
     {:get_map_elements, {:f, 16},
      {:tr, {:x, 0}, {{:t_map, :any, :any}, 0, 18_446_744_073_709_551_615}},
      {:list, [atom: :b, x: 2]}},
     {:line, 3},
     {:gc_bif, :+, {:f, 0}, 3, [x: 1, x: 2], {:x, 0}},
     :return,
     {:label, 16},
     {:test_heap, 4, 1},
     {:put_tuple2, {:x, 0}, {:list, [atom: :badkey, atom: :b, x: 0]}},
     {:call_ext_only, 1, {:extfunc, :erlang, :error, 1}},
     {:label, 17},
     {:test_heap, 4, 1},
     {:put_tuple2, {:x, 0}, {:list, [atom: :badkey, atom: :a, x: 0]}},
     {:call_ext_only, 1, {:extfunc, :erlang, :error, 1}}
   ]}

  def dottet_access(%__MODULE__{} = struct) do
    struct.a + struct.b
  end

  {:function, :mixed, 1, 23,
   [
     {:line, 9},
     {:label, 22},
     {:func_info, {:atom, Test}, {:atom, :mixed}, 1},
     {:label, 23},
     {:test, :is_map, {:f, 22}, [x: 0]},
     {:get_map_elements, {:f, 22},
      {:tr, {:x, 0}, {{:t_map, :any, :any}, 0, 18_446_744_073_709_551_615}},
      {:list, [atom: :__struct__, x: 1]}},
     {:test, :is_eq_exact, {:f, 22}, [x: 1, atom: Test]},
     {:get_map_elements, {:f, 24},
      {:tr, {:x, 0}, {{:t_map, :any, :any}, 0, 18_446_744_073_709_551_615}},
      {:list, [atom: :b, x: 2, atom: :a, x: 1]}},
     {:line, 10},
     {:gc_bif, :+, {:f, 0}, 3, [x: 1, x: 2], {:x, 0}},
     :return,
     {:label, 24},
     {:line, 11},
     {:badmatch, {:x, 0}}
   ]}

  def mixed(%__MODULE__{} = struct) do
    %{a: a, b: b} = struct
    a + b
  end

  {:function, :map_access, 1, 18,
   [
     {:line, 4},
     {:label, 17},
     {:func_info, {:atom, Test}, {:atom, :map_access}, 1},
     {:label, 18},
     {:allocate, 1, 1},
     {:move, {:x, 0}, {:y, 0}},
     {:move, {:atom, :a}, {:x, 1}},
     {:line, 5},
     {:call_ext, 2, {:extfunc, Map, :get, 2}},
     {:swap, {:y, 0}, {:x, 0}},
     {:move, {:atom, :b}, {:x, 1}},
     {:call_ext, 2, {:extfunc, Map, :get, 2}},
     {:gc_bif, :+, {:f, 0}, 1, [y: 0, x: 0], {:x, 0}},
     {:deallocate, 1},
     :return
   ]}

  def map_access(struct) do
    Map.get(struct, :a) + Map.get(struct, :b)
  end
end
{:module, name, _binary, _bindings} =
  defmodule Benchmark do
    use BencheeDsl.Benchmark
    config(warmup: 5, time: 10, pre_check: true, print: [configuration: false])
    inputs(%{"input" => %UnderTest{a: 1, b: 2}})

    job(dottet_access(struct)) do
      UnderTest.dottet_access(struct)
    end

    job(unpack(struct)) do
      UnderTest.unpack(struct)
    end

    job(map_access(struct)) do
      UnderTest.map_access(struct)
    end

    job(match(struct)) do
      UnderTest.match(struct)
    end

    job(mixed(struct)) do
      UnderTest.mixed(struct)
    end
  end

BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render()
##### With input input #####
Name ips average deviation median 99th %
unpack 2.67 M 374.11 ns ±1629.96% 357 ns 532 ns
match 2.60 M 384.25 ns ±1755.78% 359 ns 581 ns
mixed 2.57 M 388.85 ns ±1691.96% 360 ns 635 ns
dottet_access 2.55 M 392.23 ns ±1685.90% 367 ns 648 ns
map_access 2.47 M 404.81 ns ±1596.56% 378 ns 656 ns
Comparison:
unpack 2.67 M
match 2.60 M - 1.03x slower +10.14 ns
mixed 2.57 M - 1.04x slower +14.73 ns
dottet_access 2.55 M - 1.05x slower +18.11 ns
map_access 2.47 M - 1.08x slower +30.70 ns
@asakura
Copy link
Copy Markdown
Author

asakura commented Jun 22, 2023

Sure, will do!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment