Yep, I remember the talk about that optimization pass on the OTP repo if I recall correctly…
Let’s test it!! ^.^
Given this test:
defmodule MapMergePutBench do
@classifiers [2, 10, 100, 1000]
def classifiers(), do: @classifiers
def time(_), do: 2
def inputs(cla),
do: %{
"Orig:0" => %{},
"Orig:1" => %{},
"Orig:#{cla}" => Map.new(Enum.map(1..cla, &{&1, &1})),
}
def setup(_cla), do: nil
def teardown(_cla, _setup), do: nil
for cla <- @classifiers do
def actions(unquote(cla), _setup),
do: %{
"Map.merge" => fn inp -> inp |> Map.merge(unquote({:%{}, [], Enum.map(1..cla, &{&1, &1+1})})) end,
"Map.put" => fn inp -> inp |> unquote(Enum.reduce(
2..cla,
{{:., [], [{:__aliases__, [alias: false], [:Map]}, :put]}, [], [1, 2]},
&{:|>, [context: Elixir, import: Kernel], [&2, {{:., [], [{:__aliases__, [alias: false], [:Map]}, :put]}, [], [&1, &1+1]}]}
)) end,
}
end
end
Which should properly inline the merge and put calls with inlined data as it is the usual optimal condition (as in the OP in this thread), let’s test it at a variety of sizes!
Raw results:
╰─➤ mix bench map_merge_put
Benchmarking Classifier: 2
==========================
Operating System: Linux"
CPU Information: AMD Phenom(tm) II X6 1090T Processor
Number of Available Cores: 6
Available memory: 15.67 GB
Elixir 1.8.0-rc.1
Erlang 21.2.2
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 2 s
memory time: 2 s
parallel: 1
inputs: Orig:0, Orig:1, Orig:2
Estimated total run time: 36 s
Benchmarking Map.merge with input Orig:0...
Benchmarking Map.merge with input Orig:1...
Benchmarking Map.merge with input Orig:2...
Benchmarking Map.put with input Orig:0...
Benchmarking Map.put with input Orig:1...
Benchmarking Map.put with input Orig:2...
##### With input Orig:0 #####
Name ips average deviation median 99th %
Map.put 15.11 M 0.0662 μs ±598.21% 0.0600 μs 0.120 μs
Map.merge 15.09 M 0.0663 μs ±487.88% 0.0600 μs 0.140 μs
Comparison:
Map.put 15.11 M
Map.merge 15.09 M - 1.00x slower
Memory usage statistics:
Name Memory usage
Map.put 200 B
Map.merge 200 B - 1.00x memory usage
**All measurements for memory usage were the same**
##### With input Orig:1 #####
Name ips average deviation median 99th %
Map.merge 15.12 M 0.0661 μs ±551.74% 0.0600 μs 0.140 μs
Map.put 15.04 M 0.0665 μs ±458.40% 0.0600 μs 0.140 μs
Comparison:
Map.merge 15.12 M
Map.put 15.04 M - 1.01x slower
Memory usage statistics:
Name Memory usage
Map.merge 200 B
Map.put 200 B - 1.00x memory usage
**All measurements for memory usage were the same**
##### With input Orig:2 #####
Name ips average deviation median 99th %
Map.put 14.51 M 0.0689 μs ±460.88% 0.0600 μs 0.170 μs
Map.merge 13.79 M 0.0725 μs ±509.28% 0.0700 μs 0.150 μs
Comparison:
Map.put 14.51 M
Map.merge 13.79 M - 1.05x slower
Memory usage statistics:
Name Memory usage
Map.put 280 B
Map.merge 280 B - 1.00x memory usage
**All measurements for memory usage were the same**
Benchmarking Classifier: 10
===========================
Operating System: Linux"
CPU Information: AMD Phenom(tm) II X6 1090T Processor
Number of Available Cores: 6
Available memory: 15.67 GB
Elixir 1.8.0-rc.1
Erlang 21.2.2
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 2 s
memory time: 2 s
parallel: 1
inputs: Orig:0, Orig:1, Orig:10
Estimated total run time: 36 s
Benchmarking Map.merge with input Orig:0...
Benchmarking Map.merge with input Orig:1...
Benchmarking Map.merge with input Orig:10...
Benchmarking Map.put with input Orig:0...
Benchmarking Map.put with input Orig:1...
Benchmarking Map.put with input Orig:10...
##### With input Orig:0 #####
Name ips average deviation median 99th %
Map.put 11.24 M 0.0890 μs ±325.53% 0.0800 μs 0.180 μs
Map.merge 10.84 M 0.0922 μs ±315.66% 0.0800 μs 0.190 μs
Comparison:
Map.put 11.24 M
Map.merge 10.84 M - 1.04x slower
Memory usage statistics:
Name Memory usage
Map.put 328 B
Map.merge 328 B - 1.00x memory usage
**All measurements for memory usage were the same**
##### With input Orig:1 #####
Name ips average deviation median 99th %
Map.put 11.21 M 0.0892 μs ±329.04% 0.0800 μs 0.180 μs
Map.merge 10.71 M 0.0934 μs ±329.74% 0.0800 μs 0.20 μs
Comparison:
Map.put 11.21 M
Map.merge 10.71 M - 1.05x slower
Memory usage statistics:
Name Memory usage
Map.put 328 B
Map.merge 328 B - 1.00x memory usage
**All measurements for memory usage were the same**
##### With input Orig:10 #####
Name ips average deviation median 99th %
Map.merge 6.36 M 0.157 μs ±3498.41% 0.100 μs 0.50 μs
Map.put 6.27 M 0.160 μs ±3496.35% 0.100 μs 0.50 μs
Comparison:
Map.merge 6.36 M
Map.put 6.27 M - 1.02x slower
Memory usage statistics:
Name Memory usage
Map.merge 600 B
Map.put 600 B - 1.00x memory usage
**All measurements for memory usage were the same**
Benchmarking Classifier: 100
============================
Operating System: Linux"
CPU Information: AMD Phenom(tm) II X6 1090T Processor
Number of Available Cores: 6
Available memory: 15.67 GB
Elixir 1.8.0-rc.1
Erlang 21.2.2
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 2 s
memory time: 2 s
parallel: 1
inputs: Orig:0, Orig:1, Orig:100
Estimated total run time: 36 s
Benchmarking Map.merge with input Orig:0...
Benchmarking Map.merge with input Orig:1...
Benchmarking Map.merge with input Orig:100...
Benchmarking Map.put with input Orig:0...
Benchmarking Map.put with input Orig:1...
Benchmarking Map.put with input Orig:100...
##### With input Orig:0 #####
Name ips average deviation median 99th %
Map.merge 85.46 K 11.70 μs ±13.20% 11 μs 16 μs
Map.put 84.42 K 11.85 μs ±16.17% 11 μs 17 μs
Comparison:
Map.merge 85.46 K
Map.put 84.42 K - 1.01x slower
Memory usage statistics:
Name Memory usage
Map.merge 1.73 KB
Map.put 1.73 KB - 1.00x memory usage
**All measurements for memory usage were the same**
##### With input Orig:1 #####
Name ips average deviation median 99th %
Map.merge 84.65 K 11.81 μs ±68.72% 12 μs 17 μs
Map.put 82.88 K 12.07 μs ±19.01% 12 μs 19 μs
Comparison:
Map.merge 84.65 K
Map.put 82.88 K - 1.02x slower
Memory usage statistics:
Name Memory usage
Map.merge 1.73 KB
Map.put 1.73 KB - 1.00x memory usage
**All measurements for memory usage were the same**
##### With input Orig:100 #####
Name ips average deviation median 99th %
Map.merge 111.28 K 8.99 μs ±28.99% 8 μs 17 μs
Map.put 107.92 K 9.27 μs ±35.87% 8 μs 19 μs
Comparison:
Map.merge 111.28 K
Map.put 107.92 K - 1.03x slower
Memory usage statistics:
Name Memory usage
Map.merge 4.52 KB
Map.put 4.52 KB - 1.00x memory usage
**All measurements for memory usage were the same**
Benchmarking Classifier: 1000
=============================
Operating System: Linux"
CPU Information: AMD Phenom(tm) II X6 1090T Processor
Number of Available Cores: 6
Available memory: 15.67 GB
Elixir 1.8.0-rc.1
Erlang 21.2.2
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 2 s
memory time: 2 s
parallel: 1
inputs: Orig:0, Orig:1, Orig:1000
Estimated total run time: 36 s
Benchmarking Map.merge with input Orig:0...
Benchmarking Map.merge with input Orig:1...
Benchmarking Map.merge with input Orig:1000...
Benchmarking Map.put with input Orig:0...
Benchmarking Map.put with input Orig:1...
Benchmarking Map.put with input Orig:1000...
##### With input Orig:0 #####
Name ips average deviation median 99th %
Map.put 6.63 K 150.83 μs ±4.68% 148 μs 177 μs
Map.merge 6.43 K 155.56 μs ±7.29% 150 μs 191 μs
Comparison:
Map.put 6.63 K
Map.merge 6.43 K - 1.03x slower
Memory usage statistics:
Name Memory usage
Map.put 20.24 KB
Map.merge 20.24 KB - 1.00x memory usage
**All measurements for memory usage were the same**
##### With input Orig:1 #####
Name ips average deviation median 99th %
Map.merge 6.64 K 150.53 μs ±4.67% 148 μs 174 μs
Map.put 6.60 K 151.62 μs ±7.92% 148 μs 178 μs
Comparison:
Map.merge 6.64 K
Map.put 6.60 K - 1.01x slower
Memory usage statistics:
Name Memory usage
Map.merge 20.24 KB
Map.put 20.24 KB - 1.00x memory usage
**All measurements for memory usage were the same**
##### With input Orig:1000 #####
Name ips average deviation median 99th %
Map.put 4.42 K 226.17 μs ±6.91% 219 μs 284 μs
Map.merge 4.29 K 232.85 μs ±8.26% 225 μs 306 μs
Comparison:
Map.put 4.42 K
Map.merge 4.29 K - 1.03x slower
Memory usage statistics:
Name Memory usage
Map.put 32.36 KB
Map.merge 32.36 KB - 1.00x memory usage
**All measurements for memory usage were the same**
So in summary:
Original Map Size |
put/merge |
Map Updates |
Ops/sec |
Difference |
Mem |
0 |
put |
2 |
15.11 M |
1.00x |
200 B |
0 |
merge |
2 |
15.09 M |
1.00x |
200 B |
1 |
merge |
2 |
15.12 M |
1.00x |
200 B |
1 |
put |
2 |
15.04 M |
1.01x |
200 B |
2 |
put |
2 |
14.51 M |
1.00x |
280 B |
2 |
merge |
2 |
13.79 M |
1.05x |
280 B |
0 |
put |
10 |
11.24 M |
1.00x |
328 B |
0 |
merge |
10 |
10.84 M |
1.04x |
328 B |
1 |
put |
10 |
11.21 M |
1.00x |
328 B |
1 |
merge |
10 |
10.71 M |
1.05x |
328 B |
10 |
merge |
10 |
6.36 M |
1.00x |
600 B |
10 |
put |
10 |
6.27 M |
1.02x |
600 B |
0 |
merge |
100 |
85.46 K |
1.00x |
1.73 K |
0 |
put |
100 |
84.42 K |
1.01x |
1.73 K |
1 |
merge |
100 |
84.65 K |
1.00x |
1.73 K |
1 |
put |
100 |
82.88 K |
1.02x |
1.73 K |
100 |
merge |
100 |
111.28 K |
1.00x |
4.52 K |
100 |
put |
100 |
107.92 K |
1.03x |
4.52 K |
0 |
put |
1000 |
6.63 K |
1.00x |
20.24 K |
0 |
merge |
1000 |
6.43 K |
1.03x |
20.24 K |
1 |
merge |
1000 |
6.64 K |
1.00x |
20.24 K |
1 |
put |
1000 |
6.60 K |
1.01x |
20.24 K |
1000 |
put |
1000 |
4.42 K |
1.00x |
32.36 K |
1000 |
merge |
1000 |
4.29 K |
1.03x |
32.36 K |
In other words they are about identical, well within error bounds, just outright well within the error bounds (deviation was averaging around 5%, even with that they were almost identical as it was). And I’m sure checking the generated code will probably show the same beam disassembly as well.
So yeah, use whichever is cleaner for your code.