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.
$ 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
I think what @jimdigriz was trying to say here is, yes they are equivalent
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.
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.
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%
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.
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!
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
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.
...