What is the most performant way to create a map?

In a case you have to create a Map with 15 ~ 20 fields, who is more performatic? A hardcoded map

%{foo: 1, bar: 2, baz: 3 ...}

Or builda Map using Map.put

%{}
|> Map.put(:foo, 1)
|> Map.put(:bar, 2)
...

First one.

2 Likes

you could test using benchee | Hex - most likely hardcoded, for obvious reasons…

2 Likes

Depends on the exact details of the keys and values, but there’s less difference between the two than you’d think if things are compile-time constants.

Here’s a module to demonstrate that:

defmodule Bar do
  @compile :S

  def bar do
    %{}
    |> Map.put(:a, 1)
    |> Map.put(:b, 2)
  end

  def baz do
    %{a: 1, b: 2}
  end

  def huh(a, b) do
    %{a: a, b: b}
  end

  def wat(a, b) do
    %{}
    |> Map.put(:a, a)
    |> Map.put(:b, b)
  end
end

The @compile :S attribute causes the compiler to emit a BEAM assembly file with a .S extension and then crash. That file is a human-readable version of what the compiler converts to a .beam artifact. Check out the BEAM Book for details on exactly what’s happening, but you don’t need to be fluent in the VM bytecode to draw conclusions.

First observation: baz’s implementation is a single move from the literal pool. Since the Map is immutable, every call to baz can return a reference to the same structure.

Second observation: bar’s implementation is… ALSO a single move from the literal pool. The compiler has noticed that the expression in bar returns a constant value and pre-calculated it at compile-time. So the answer to your original specific question is “there’s absolutely no difference”, since the two forms compile to exactly the same assembly.

Third observation: the implementation in huh uses a single put_map_assoc instruction that combines the literal keys and the input arguments.

Final observation: the implementation in wat is optimized to match huh exactly! Again, the compiler has observed that the result can be pre-computed.

You can experiment with the limits of this optimization; for instance, adding a Map.put to wats chain with a non-constant key will force two put_map_assoc calls to be used.

17 Likes