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
I didn't want to mention this in the last PR.
It seems that each dot access to a struct compiles to a single :get_map_elements instruction. So
unpack/1
gets all map fields in one go anddottet_access/1
call :get_map_elements three times. Executing three instructions instead of one explains 5% difference in run time.That behavior actually can be explained. The Erlang's compiler never tracks if a map actually has specific fields or not, so it has to generate error handling code:
Which throws :badkey error like that:
On the other hand when matching happens in function's head it doesn't matter if :get_map_elements fails to extract all requested fields - execution flow goes to the next head and so on until it out of options:
What do you think @mtrudel? Is it worth to thin down ThousandIsland.Socket module in exchange for using more verbose syntax?