Is test(Map = #{}) -> ok. the same as test(Map) when is_map(Map) -> ok.?

If not, which one is more efficient? If yes, would you like to point to the source code that does this optimization? Thanks in advance.

1 Like
$ cat <<'EOF' > test.erl
-module(test).

-export([noguard/1, guard/1]).

noguard(_Map = #{}) -> ok.

guard(Map) when is_map(Map) -> ok.
EOF

$ erl
Erlang/OTP 25 [erts-13.1.5] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Eshell V13.1.5  (abort with ^G)
1> compile:file(test, ['S']).
{ok,test}
2> q().

$ cat test.S
4 Likes

I think what @jimdigriz was trying to say here is, yes they are equivalent :slight_smile:

2 Likes

Thanks for the replies. I tried the test code using c(test,‘S’) and they are equivalent, using is_map to check. But if I use c(test,to_core) and the output code are different:
test(Map = #{}) → ok.
has Map = ~{}~ in its code. That looks like constructing an empty map then match it to Map.
I am curious where in the compiler source code optimizes this constructing an empty map step into directly calling is_map.

1 Like

You’re going to want to study : otp/beam_core_to_ssa.erl at master · erlang/otp · GitHub

Edit:

I think one of the lines you’re interested in is going to be here

However, there’s a lot going on and I’ve never studied this code in anger, thus I would ask @bjorng (who also may have the best avatar ever!) to confirm or deny this, and perhaps breakdown the paths within this module for the case you’re interested in.

2 Likes

I don’t think there is any significant difference:

./erlperf 'test:guard(#{}).' 'test:noguard(#{}).' -s 100 -d 100
Code                   ||   Samples       Avg   StdDev    Median      P99  Iteration    Rel
test:guard(#{}).        1       100  12255 Ki    0.24%  12251 Ki 12320 Ki       8 ns   100%
test:noguard(#{}).      1       100  12204 Ki    0.36%  12215 Ki 12238 Ki       8 ns   100%
5 Likes

Yes, that is where the SSA code for matching a map is generated. The first instruction for matching any map is always the is_map test.

Another line that may be interesting is the start of the pattern matching compilation.

In this context, Map = ~{}~ means matching of a map, not constructing an empty map. As I mentioned earlier, the matching of any map starts with the is_map test, so this is not an optimization.

2 Likes

Thanks for all the replies and links. It is very helpful. beam_core_to_ssa.erl looks like quite complex. Lucky we don’t need to master this to write efficient Erlang code. Thanks Erlang/OTP team for doing all these heavy lifting implementations!

3 Likes

You can also use this online tool to explore the result of various compilation stages. There you may see that both functions start to look equivalent in the SSA stage

4 Likes

Or

$ erlc -S test.erl
$ cat test.S
...
{function, noguard, 1, 2}.
  {label,1}.
    {line,[{location,"test.erl",5}]}.
    {func_info,{atom,test},{atom,noguard},1}.
  {label,2}.
    {test,is_map,{f,1},[{x,0}]}.
    {move,{atom,ok},{x,0}}.
    return.


{function, guard, 1, 4}.
  {label,3}.
    {line,[{location,"test.erl",7}]}.
    {func_info,{atom,test},{atom,guard},1}.
  {label,4}.
    {test,is_map,{f,3},[{x,0}]}.
    {move,{atom,ok},{x,0}}.
    return.
...
2 Likes