diff options
author | John Högberg <john@erlang.org> | 2021-01-14 11:19:36 +0100 |
---|---|---|
committer | John Högberg <john@erlang.org> | 2021-01-22 10:53:01 +0100 |
commit | c0d65e0d400af75e76bf30001f1c2d85709a12a5 (patch) | |
tree | 466e1bc038d4e937d9b032239b3d0dad4ab8fa5d | |
parent | 9ffab401c69fe41db473409b04338e24c0980ad0 (diff) | |
download | erlang-c0d65e0d400af75e76bf30001f1c2d85709a12a5.tar.gz |
compiler: Introduce +recv_opt_info
-rw-r--r-- | lib/compiler/doc/src/compile.xml | 10 | ||||
-rw-r--r-- | lib/compiler/src/beam_kernel_to_ssa.erl | 9 | ||||
-rw-r--r-- | lib/compiler/src/beam_ssa_recv.erl | 159 | ||||
-rw-r--r-- | lib/compiler/src/v3_core.erl | 7 | ||||
-rw-r--r-- | lib/compiler/test/compilation_SUITE.erl | 5 | ||||
-rw-r--r-- | lib/compiler/test/compile_SUITE.erl | 3 | ||||
-rw-r--r-- | lib/compiler/test/inline_SUITE.erl | 8 | ||||
-rw-r--r-- | lib/compiler/test/test_lib.erl | 4 | ||||
-rw-r--r-- | lib/compiler/test/warnings_SUITE.erl | 61 | ||||
-rw-r--r-- | system/doc/efficiency_guide/efficiency_guide.erl | 42 | ||||
-rw-r--r-- | system/doc/efficiency_guide/processes.xml | 130 |
11 files changed, 398 insertions, 40 deletions
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index 0e2f29aa9d..8329ed867c 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -314,6 +314,16 @@ module.beam: module.erl \ </p> </item> + <tag><c>recv_opt_info</c></tag> + <item> + <p>The compiler will emit informational warnings about selective + receive optimizations (both successful and unsuccessful). For + more information, see the section about + <seeguide marker="system/efficiency_guide:processes#receiving-messages"> + selective receive optimization</seeguide> in the Efficiency + Guide.</p> + </item> + <tag><c>report_errors/report_warnings</c></tag> <item> <p>Causes errors/warnings to be printed as they occur.</p> diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl index 86aaad7f66..2d3e6b730a 100644 --- a/lib/compiler/src/beam_kernel_to_ssa.erl +++ b/lib/compiler/src/beam_kernel_to_ssa.erl @@ -724,11 +724,14 @@ internal_cg(_Anno, raise, As, [#k_var{name=Dst0}], St0) -> Is = [Resume,make_uncond_branch(Fail),#cg_unreachable{}], {Is,St} end; -internal_cg(_Anno, recv_peek_message, [], [#k_var{name=Succeeded0}, - #k_var{name=Dst0}], St0) -> +internal_cg(Anno, recv_peek_message, [], [#k_var{name=Succeeded0}, + #k_var{name=Dst0}], St0) -> {Dst,St1} = new_ssa_var(Dst0, St0), St = new_succeeded_value(Succeeded0, Dst, St1), - Set = #b_set{op=peek_message,dst=Dst,args=[#b_literal{val=none}]}, + Set = #b_set{ anno=Anno, + op=peek_message, + dst=Dst, + args=[#b_literal{val=none}] }, {[Set],St}; internal_cg(_Anno, recv_wait_timeout, As, [#k_var{name=Succeeded0}], St0) -> %% Note that the `wait_timeout` instruction can potentially branch in three diff --git a/lib/compiler/src/beam_ssa_recv.erl b/lib/compiler/src/beam_ssa_recv.erl index f84708435d..3bbdd4683e 100644 --- a/lib/compiler/src/beam_ssa_recv.erl +++ b/lib/compiler/src/beam_ssa_recv.erl @@ -19,7 +19,7 @@ %% -module(beam_ssa_recv). --export([module/2]). +-export([format_error/1, module/2]). %%% %%% In code such as: @@ -106,26 +106,40 @@ -import(lists, [foldl/3, search/2]). +-spec format_error(term()) -> nonempty_string(). + +format_error(OptInfo) -> + format_opt_info(OptInfo). + -record(scan, { graph=beam_digraph:new(), module :: #{ beam_ssa:b_local() => {beam_ssa:block_map(), [beam_ssa:b_var()]} }, recv_candidates=#{}, ref_candidates=#{} }). --spec module(beam_ssa:b_module(), [compile:option()]) -> - {'ok',beam_ssa:b_module()}. +-spec module(Module, Options) -> Result when + Module :: beam_ssa:b_module(), + Options :: [compile:option()], + Result :: {ok, beam_ssa:b_module(), list()}. -module(#b_module{}=Mod, _Options) -> +module(#b_module{}=Mod0, Opts) -> %% Builds a module-wide graph of all blocks (including calls between %% functions), and collects all suitable reference creations and receives %% for later analysis. - Scan = scan(Mod), + Scan = scan(Mod0), %% Figures out where to place marker creation, usage, and clearing by %% walking through the module-wide graph. {Markers, Uses, Clears} = plan(Scan), - optimize(Mod, Markers, Uses, Clears). + Mod = optimize(Mod0, Markers, Uses, Clears), + + Ws = case proplists:get_bool(recv_opt_info, Opts) of + true -> collect_opt_info(Mod); + false -> [] + end, + + {ok, Mod, Ws}. scan(#b_module{body=Fs0}) -> ModMap = foldl(fun(#b_function{bs=Blocks,args=Args}=F, Acc) -> @@ -181,17 +195,15 @@ scan_is([#b_set{op=bs_put}], Blk, Lbl, Blocks, FuncId, State) -> %% This may throw and returns a success variable, so we'll treat it as %% that. scan_is([#b_set{op={succeeded,body}}], Blk, Lbl, Blocks, FuncId, State); -scan_is([#b_set{op=call,dst=Dst,args=[#b_remote{}=Callee | _]} | Is], +scan_is([#b_set{op=call,args=[#b_remote{} | _]}=Call | Is], Blk, Lbl, Blocks, FuncId, State0) -> case {Is, Blk#b_blk.last} of {[#b_set{op={succeeded,body}}], #b_br{bool=Bool,succ=Succ}} -> #b_var{} = Bool, %Assertion. - State = si_remote_call(Dst, Callee, Lbl, Succ, - Blocks, FuncId, State0), + State = si_remote_call(Call, Lbl, Succ, Blocks, FuncId, State0), scan_is(Is, Blk, Lbl, Blocks, FuncId, State); _ -> - State = si_remote_call(Dst, Callee, Lbl, Lbl, - Blocks, FuncId, State0), + State = si_remote_call(Call, Lbl, Lbl, Blocks, FuncId, State0), scan_is(Is, Blk, Lbl, Blocks, FuncId, State) end; scan_is([#b_set{op=call,args=[#b_local{}=Callee | Args]} | Is], @@ -205,7 +217,7 @@ scan_is([], Blk, Lbl, _Blocks, FuncId, State) -> scan_add_edge({FuncId, Lbl}, {FuncId, Succ}, Acc) end, State, beam_ssa:successors(Blk)). -%% Adds an edge to the callee, with parameter/argument translation to let us +%% Adds an edge to the callee, with argument/parameter translation to let us %% follow specific references. scan_add_call(Args, Callee, Lbl, Caller, #scan{module=ModMap}=State0) -> #{ Callee := {_Blocks, Params} } = ModMap, @@ -238,13 +250,14 @@ scan_add_vertex(Vertex, #scan{graph=Graph0}=State) -> State#scan{graph=Graph} end. -si_remote_call(Dst, Callee, CreatedAt, ValidAfter, Blocks, FuncId, State) -> +si_remote_call(Call, CreatedAt, ValidAfter, Blocks, FuncId, State) -> + #b_set{anno=Anno,op=call,dst=Dst,args=[#b_remote{}=Callee | _]}=Call, case si_makes_ref(Dst, ValidAfter, Callee, Blocks) of {ExtractedAt, Ref} -> #scan{ref_candidates=Candidates0} = State, MakeRefs0 = maps:get(FuncId, Candidates0, []), - MakeRef = {CreatedAt, Dst, ExtractedAt, Ref}, + MakeRef = {Anno, CreatedAt, Dst, ExtractedAt, Ref}, Candidates = Candidates0#{ FuncId => [MakeRef | MakeRefs0] }, @@ -334,7 +347,7 @@ plan(Scan) -> propagate_references(Candidates, G) -> Roots = maps:fold(fun(FuncId, MakeRefs, Acc) -> [begin - {_, _, ExtractedAt, Ref} = MakeRef, + {_, _, _, ExtractedAt, Ref} = MakeRef, Vertex = {FuncId, ExtractedAt}, {Vertex, Ref} end || MakeRef <- MakeRefs] ++ Acc @@ -573,7 +586,7 @@ plan_markers(Candidates, UsageMap) -> end, #{}, Candidates). plan_markers_1(MakeRefs0, FuncId, UsageMap) -> - [Marker || {_, _, ExtractedAt, Ref}=Marker <- MakeRefs0, + [Marker || {_, _, _, ExtractedAt, Ref}=Marker <- MakeRefs0, case UsageMap of #{ {FuncId, ExtractedAt} := Refs } -> sets:is_element(Ref, Refs); @@ -613,7 +626,7 @@ plan_clears_1([], _ActiveRefs, _UsageMap) -> optimize(#b_module{body=Fs0}=Mod, Markers, Uses, Clears) -> Fs = [optimize_1(F, Markers, Uses, Clears) || F <- Fs0], - {ok, Mod#b_module{body=Fs}}. + Mod#b_module{body=Fs}. optimize_1(#b_function{bs=Blocks0,cnt=Count0}=F, Markers, Uses, Clears) -> FuncId = get_func_id(F), @@ -627,23 +640,23 @@ optimize_1(#b_function{bs=Blocks0,cnt=Count0}=F, Markers, Uses, Clears) -> F#b_function{bs=Blocks,cnt=Count}. -insert_markers([{CreatedAt, Dst, ExtractedAt, Ref} | Markers], +insert_markers([{Anno, CreatedAt, Dst, ExtractedAt, Ref} | Markers], Blocks0, Count0) -> {MarkerVar, Blocks1, Count1} = - insert_reserve(CreatedAt, Dst, Blocks0, Count0), + insert_reserve(CreatedAt, Dst, Anno, Blocks0, Count0), {Blocks, Count} = insert_bind(ExtractedAt, Ref, MarkerVar, Blocks1, Count1), insert_markers(Markers, Blocks, Count); insert_markers([], Blocks, Count) -> {Blocks, Count}. -insert_reserve(Lbl, Dst, Blocks0, Count0) -> +insert_reserve(Lbl, Dst, Anno, Blocks0, Count0) -> #{ Lbl := #b_blk{is=Is0}=Blk } = Blocks0, Var = #b_var{name={'@ssa_recv_marker', Count0}}, Count = Count0 + 1, - Reserve = #b_set{op=recv_marker_reserve,args=[],dst=Var}, + Reserve = #b_set{anno=Anno,op=recv_marker_reserve,args=[],dst=Var}, Is = insert_reserve_is(Is0, Reserve, Dst), Blocks = Blocks0#{ Lbl := Blk#b_blk{is=Is} }, @@ -703,3 +716,107 @@ insert_clears_1([{From, To, Ref} | Clears], Count0, Acc) -> insert_clears_1(Clears, Count, [{From, To, [Clear]} | Acc]); insert_clears_1([], Count, Acc) -> {Acc, Count}. + +%%% +%%% +recv_opt_info +%%% + +collect_opt_info(#b_module{body=Fs}) -> + coi_1(Fs, []). + +coi_1([#b_function{args=Args,bs=Blocks}=F | Fs], Acc0) -> + Lbls = beam_ssa:rpo(Blocks), + Where = beam_ssa:get_anno(location, F, []), + {Defs, _} = foldl(fun(Var, {Defs0, Index0}) -> + Defs = Defs0#{ Var => {parameter, Index0}}, + Index = Index0 + 1, + {Defs, Index} + end, {#{}, 1}, Args), + Acc = coi_bs(Lbls, Blocks, Where, Defs, Acc0), + coi_1(Fs, Acc); +coi_1([], Acc) -> + Acc. + +coi_bs([Lbl | Lbls], Blocks, Where, Defs0, Ws0) -> + #{ Lbl := #b_blk{is=Is,last=Last} } = Blocks, + {Defs, Ws} = coi_is(Is, Last, Blocks, Where, Defs0, Ws0), + coi_bs(Lbls, Blocks, Where, Defs, Ws); +coi_bs([], _Blocks, _Where, _Defs, Ws) -> + Ws. + +coi_is([#b_set{anno=Anno,op=peek_message,args=[#b_var{}]=Args } | Is], + Last, Blocks, Where, Defs, Ws) -> + [Creation] = coi_creations(Args, Blocks, Defs), + Warning = make_warning({used_receive_marker, Creation}, Anno, Where), + coi_is(Is, Last, Blocks, Where, Defs, [Warning | Ws]); +coi_is([#b_set{anno=Anno,op=peek_message,args=[#b_literal{}] } | Is], + Last, Blocks, Where, Defs, Ws) -> + + %% Is this a selective receive? + #b_br{succ=NextMsg} = Last, + #{ NextMsg := #b_blk{is=NextIs} } = Blocks, + Info = case NextIs of + [#b_set{op=remove_message} | _] -> matches_any_message; + _ -> unoptimized_selective_receive + end, + + Warning = make_warning(Info, Anno, Where), + coi_is(Is, Last, Blocks, Where, Defs, [Warning | Ws]); +coi_is([#b_set{anno=Anno,op=recv_marker_reserve} | Is], + Last, Blocks, Where, Defs, Ws) -> + Warning = make_warning(reserved_receive_marker, Anno, Where), + coi_is(Is, Last, Blocks, Where, Defs, [Warning | Ws]); +coi_is([#b_set{anno=Anno,op=call,dst=Dst,args=[#b_local{} | Args] }=I | Is], + Last, Blocks, Where, Defs0, Ws0) -> + Defs = Defs0#{ Dst => I }, + Ws = [make_warning({passed_marker, Creation}, Anno, Where) + || #b_set{}=Creation <- coi_creations(Args, Blocks, Defs)] ++ Ws0, + coi_is(Is, Last, Blocks, Where, Defs, Ws); +coi_is([#b_set{dst=Dst}=I | Is], Last, Blocks, Where, Defs0, Ws) -> + Defs = Defs0#{ Dst => I }, + coi_is(Is, Last, Blocks, Where, Defs, Ws); +coi_is([], _Last, _Blocks, _Where, Defs, Ws) -> + {Defs, Ws}. + +coi_creations([Arg | Args], Blocks, Defs) -> + case Defs of + #{ Arg := #b_set{op=call,dst=Dst,args=[#b_remote{}=Callee|_]}=Call } -> + case si_makes_ref(Dst, 0, Callee, Blocks) of + {_, _} -> + [Call | coi_creations(Args, Blocks, Defs)]; + no -> + coi_creations(Args, Blocks, Defs) + end; + #{ Arg := #b_set{op=get_tuple_element,args=[Tuple|_]}} -> + coi_creations([Tuple | Args], Blocks, Defs); + #{ Arg := {parameter, _}=Parameter } -> + [Parameter | coi_creations(Args, Blocks, Defs)]; + #{} -> + coi_creations(Args, Blocks, Defs) + end; +coi_creations([], _Blocks, _Defs) -> + []. + +make_warning(Term, Anno, Where) -> + {File, Line} = maps:get(location, Anno, Where), + {File,[{Line,?MODULE,Term}]}. + +format_opt_info(matches_any_message) -> + "INFO: receive matches any message, this is always fast"; +format_opt_info({passed_marker, Creation}) -> + io_lib:format("INFO: passing reference ~ts", + [format_ref_creation(Creation)]); +format_opt_info({used_receive_marker, Creation}) -> + io_lib:format("OPTIMIZED: all clauses match reference ~ts", + [format_ref_creation(Creation)]); +format_opt_info(reserved_receive_marker) -> + "OPTIMIZED: reference used to mark a message queue position"; +format_opt_info(unoptimized_selective_receive) -> + "NOT OPTIMIZED: all clauses do not match a suitable reference". + +format_ref_creation({parameter, Index}) -> + io_lib:format("in function parameter ~w", [Index]); +format_ref_creation(#b_set{op=call,anno=Anno,args=[Callee|_]}) -> + #b_remote{name=#b_literal{val=F},arity=A} = Callee, + {File, Line} = maps:get(location, Anno, {"",1}), + io_lib:format("created by ~p/~p at ~ts:~w", [F, A, File, Line]). diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index ae1a2cf36c..d4ee6053ab 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -3001,7 +3001,7 @@ lexpr(#c_receive{anno=RecvAnno,clauses=Cs0,timeout=Timeout0,action=Action}, St0) body=TimeoutLet}], PeekCase = #c_case{arg=PeekSucceeded,clauses=PeekCs}, PeekLet = #c_let{vars=[PeekSucceeded,Msg], - arg=primop(recv_peek_message), + arg=primop(recv_peek_message, [], RecvAnno), body=PeekCase}, Fun = #c_fun{vars=[],body=PeekLet}, @@ -3027,7 +3027,10 @@ primop(Name) -> primop(Name, []). primop(Name, Args) -> - #c_primop{name=#c_literal{val=Name},args=Args}. + primop(Name, Args, []). + +primop(Name, Args, Anno) -> + #c_primop{anno=Anno,name=#c_literal{val=Name},args=Args}. %%% %%% Split patterns such as <<Size:32,Tail:Size>> that bind diff --git a/lib/compiler/test/compilation_SUITE.erl b/lib/compiler/test/compilation_SUITE.erl index 94c857471b..8b3846bade 100644 --- a/lib/compiler/test/compilation_SUITE.erl +++ b/lib/compiler/test/compilation_SUITE.erl @@ -176,7 +176,7 @@ try_it(Module, Conf) -> Out = proplists:get_value(priv_dir,Conf), io:format("Compiling: ~s\n", [Src]), CompRc0 = compile:file(Src, [clint0,clint,ssalint,{outdir,Out},report, - bin_opt_info|OtherOpts]), + bin_opt_info,recv_opt_info|OtherOpts]), io:format("Result: ~p\n",[CompRc0]), {ok,_Mod} = CompRc0, @@ -196,7 +196,7 @@ try_it(Module, Conf) -> io:format("Compiling (with old inliner): ~s\n", [Src]), CompRc2 = compile:file(Src, [clint,ssalint, {outdir,Out},report,bin_opt_info, - {inline,1000}|OtherOpts]), + recv_opt_info,{inline,1000}|OtherOpts]), io:format("Result: ~p\n",[CompRc2]), {ok,_Mod} = CompRc2, load_and_call(Out, Module), @@ -362,6 +362,7 @@ compile_compiler(Files, OutDir, Version, InlineOpts) -> Opts = [report, clint0,clint,ssalint, bin_opt_info, + recv_opt_info, {outdir,OutDir}, {d,'COMPILER_VSN',"\""++Version++"\""}, nowarn_shadow_vars, diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 4689bb9e2a..290ed87016 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -1223,7 +1223,7 @@ warnings(_Config) -> test_lib:p_run(fun do_warnings/1, Files). do_warnings(F) -> - {ok,_,_,Ws} = compile:file(F, [binary,bin_opt_info,return]), + {ok,_,_,Ws} = compile:file(F, [binary,bin_opt_info,recv_opt_info,return]), do_warnings_1(Ws, F). do_warnings_1([{"no_file",Ws}|_], F) -> @@ -1368,6 +1368,7 @@ compiler_modules() -> env_compiler_options(_Config) -> Cases = [ {"bin_opt_info", [bin_opt_info]}, + {"recv_opt_info", [recv_opt_info]}, {"'S'", ['S']}, {"{source, \"test.erl\"}", [{source, "test.erl"}]}, {"[{d,macro_one,1},{d,macro_two}]", [{d, macro_one, 1}, {d, macro_two}]}, diff --git a/lib/compiler/test/inline_SUITE.erl b/lib/compiler/test/inline_SUITE.erl index df2d9f472b..1cb30b22da 100644 --- a/lib/compiler/test/inline_SUITE.erl +++ b/lib/compiler/test/inline_SUITE.erl @@ -92,7 +92,7 @@ try_inline(Mod, Config) -> %% Normal compilation. io:format("Compiling: ~s\n", [Src]), {ok,Mod} = compile:file(Src, [{outdir,Out},report, - bin_opt_info,clint,ssalint]), + bin_opt_info,recv_opt_info,clint,ssalint]), ct:timetrap({minutes,10}), NormalResult = load_and_call(Out, Mod), @@ -100,7 +100,7 @@ try_inline(Mod, Config) -> %% Inlining. io:format("Compiling with old inliner: ~s\n", [Src]), {ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info, - {inline,1000},clint,ssalint]), + recv_opt_info,{inline,1000},clint,ssalint]), %% Run inlined code. ct:timetrap({minutes,10}), @@ -112,8 +112,8 @@ try_inline(Mod, Config) -> %% Inlining. io:format("Compiling with new inliner: ~s\n", [Src]), - {ok,Mod} = compile:file(Src, [{outdir,Out},report, - bin_opt_info,inline,clint,ssalint]), + {ok,Mod} = compile:file(Src, [{outdir,Out},report,bin_opt_info, + inline,recv_opt_info,clint,ssalint]), %% Run inlined code. ct:timetrap({minutes,10}), diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index 212392fd57..cca81d70f7 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -38,7 +38,7 @@ recompile(Mod) when is_atom(Mod) -> %% Re-compile the test suite if the cover server is running. Beam = code:which(Mod), Src = filename:rootname(Beam, ".beam") ++ ".erl", - Opts = [bin_opt_info|opt_opts(Mod)], + Opts = [bin_opt_info,recv_opt_info|opt_opts(Mod)], io:format("Recompiling ~p (~p)\n", [Mod,Opts]), c:c(Src, [{outdir,filename:dirname(Src)}|Opts]) end, @@ -53,7 +53,7 @@ recompile_core(Mod) when is_atom(Mod) -> %% Re-compile the test suite if the cover server is running. Beam = code:which(Mod), Src = filename:rootname(Beam, ".beam"), - Opts = [bin_opt_info|opt_opts(Mod)], + Opts = [bin_opt_info,recv_opt_info|opt_opts(Mod)], io:format("Recompiling ~p (~p)\n", [Mod,Opts]), c:c(Src, [from_core,{outdir,filename:dirname(Src)}|Opts]) end, diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl index 1229a13e38..46672c3a88 100644 --- a/lib/compiler/test/warnings_SUITE.erl +++ b/lib/compiler/test/warnings_SUITE.erl @@ -42,7 +42,8 @@ comprehensions/1,maps/1,maps_bin_opt_info/1, redundant_boolean_clauses/1, latin1_fallback/1,underscore/1,no_warnings/1, - bit_syntax/1,inlining/1,tuple_calls/1]). + bit_syntax/1,inlining/1,tuple_calls/1, + recv_opt_info/1]). init_per_testcase(_Case, Config) -> Config. @@ -65,7 +66,7 @@ groups() -> maps_bin_opt_info, redundant_boolean_clauses,latin1_fallback, underscore,no_warnings,bit_syntax,inlining, - tuple_calls]}]. + tuple_calls,recv_opt_info]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -992,6 +993,62 @@ tuple_calls(Config) -> run(Config, Ts), ok. +recv_opt_info(Config) when is_list(Config) -> + Code = <<" + simple_receive() -> + receive + Message -> handle:msg(Message) + end. + + selective_receive(Tag, Message) -> + receive + {Tag, Message} -> handle:msg(Message) + end. + + cross_function_receive() -> + cross_function_receive_1(make_ref()). + + cross_function_receive_1(Tag) -> + receive + {Tag, Message} -> handle:msg(Message) + end. + + optimized_receive(Process, Request) -> + MRef = monitor(process, Process), + Process ! {self(), MRef, Request}, + receive + {MRef, Reply} -> + erlang:demonitor(MRef, [flush]), + handle:reply(Reply); + {'DOWN', MRef, _, _, Reason} -> + handle:error(Reason) + end. + ">>, + + Ws = (catch run_test(Config, Code, [recv_opt_info])), + + %% This is an inexact match since the pass reports exact instructions as + %% part of the warnings, which may include annotations that vary from run + %% to run. + {warnings, + [%% simple_receive/0 + {3,beam_ssa_recv,matches_any_message}, + %% selective_receive/2 + {8,beam_ssa_recv,unoptimized_selective_receive}, + {13,beam_ssa_recv,reserved_receive_marker}, + %% cross_function_receive/0 + {13,beam_ssa_recv,{passed_marker,_}}, + %% cross_function_receive_1/1 + {16,beam_ssa_recv,{used_receive_marker,{parameter,1}}}, + %% optimized_receive/2 + {21,beam_ssa_recv,reserved_receive_marker}, + {23,beam_ssa_recv,{used_receive_marker,_}}]} = Ws, + + %% For coverage: don't give the recv_opt_info option. + [] = (catch run_test(Config, Code, [])), + + ok. + %%% %%% End of test cases. %%% diff --git a/system/doc/efficiency_guide/efficiency_guide.erl b/system/doc/efficiency_guide/efficiency_guide.erl index c57785aaa3..52e5610c4a 100644 --- a/system/doc/efficiency_guide/efficiency_guide.erl +++ b/system/doc/efficiency_guide/efficiency_guide.erl @@ -1,5 +1,5 @@ -module(efficiency_guide). --compile([export_all,nowarn_export_all). +-compile([export_all,nowarn_export_all]). %% DO NOT naive_reverse([H|T]) -> @@ -188,5 +188,41 @@ explicit_map_pairs(Map, Xs0, Ys0) -> [] -> Ys0 end. - - + +%% DO +simple_receive() -> + receive + Message -> handle_msg(Message) + end. + +%% OK, if Tag is a reference. +selective_receive(Tag, Message) -> + receive + {Tag, Message} -> handle_msg(Message) + end. + +%% DO +optimized_receive(Process, Request) -> + MRef = monitor(process, Process), + Process ! {self(), MRef, Request}, + receive + {MRef, Reply} -> + erlang:demonitor(MRef, [flush]), + handle_reply(Reply); + {'DOWN', MRef, _, _, Reason} -> + handle_error(Reason) + end. + +%% DO +cross_function_receive() -> + Ref = make_ref(), + cross_function_receive(Ref). + +cross_function_receive(Ref) -> + receive + {Ref, Message} -> handle_msg(Message) + end. + +handle_reply(_) -> ok. +handle_error(_) -> ok. +handle_msg(_) -> ok. diff --git a/system/doc/efficiency_guide/processes.xml b/system/doc/efficiency_guide/processes.xml index db05e6a3a8..033e7f109e 100644 --- a/system/doc/efficiency_guide/processes.xml +++ b/system/doc/efficiency_guide/processes.xml @@ -144,6 +144,136 @@ loop() -> the message and distributes it to the correct process.</p> <section> + <marker id="receiving-messages"></marker> + <title>Receiving messages</title> + + <p>The cost of receiving messages depends on how complicated the + <c>receive</c> expression is. Simple expressions that match any + message are very cheap:</p> + + <p><em>DO</em></p> + <code type="erl"><![CDATA[ + receive + Message -> handle_msg(Message) + end.]]></code> + + <p>This is not always convenient however: we can receive a message that + we do not know how to handle at this point, so it's rather common to + only match the messages we expect:</p> + + <code type="erl"><![CDATA[ + receive + {Tag, Message} -> handle_msg(Message) + end.]]></code> + + <p>While this is convenient it means that the entire message queue must + be searched until it finds a matching message. This is very expensive + for processes with long message queues, so we have added an + optimization for the common case of sending a request and waiting for a + response shortly after:</p> + + <p><em>DO</em></p> + <code type="erl"><![CDATA[ + MRef = monitor(process, Process), + Process ! {self(), MRef, Request}, + receive + {MRef, Reply} -> + erlang:demonitor(MRef, [flush]), + handle_reply(Reply); + {'DOWN', MRef, _, _, Reason} -> + handle_error(Reason) + end.]]></code> + + <p>Since the compiler knows that the reference created by <c>monitor/2</c> + cannot exist before the call (since it is a globally unique identifier), + and that the <c>receive</c> only matches messages that contain said + reference, it will tell the emulator to search only the messages that + arrived after the call to <c>monitor/2</c>.</p> + + <p>The above is a simple example where one is but guaranteed that the + optimization will take, but what about more complicated code?</p> + + <section> + <marker id="recv_opt_info"></marker> + <title>Option recv_opt_info</title> + + <p>Use the <c>recv_opt_info</c> option to have the compiler print + information about receive optimizations. It can be given either to + the compiler or <c>erlc</c>:</p> + + <code type="erl"><![CDATA[ +erlc +recv_opt_info Mod.erl]]></code> + + <p>or passed through an environment variable:</p> + + <code type="erl"><![CDATA[ +export ERL_COMPILER_OPTIONS=recv_opt_info]]></code> + + <p>Notice that <c>recv_opt_info</c> is not meant to be a permanent + option added to your <c>Makefile</c>s, because all messages that it + generates cannot be eliminated. Therefore, passing the option through + the environment is in most cases the most practical approach.</p> + + <p>The warnings look as follows:</p> + + <code type="erl"><![CDATA[ +efficiency_guide.erl:194: Warning: INFO: receive matches any message, this is always fast +efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference +efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position +efficiency_guide.erl:208: Warning: OPTIMIZED: all clauses match reference created by monitor/2 at efficiency_guide.erl:206 +efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218 +efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1]]></code> + + <p>To make it clearer exactly what code the warnings refer to, the + warnings in the following examples are inserted as comments + after the clause they refer to, for example:</p> + + <code type="erl"><![CDATA[ +%% DO +simple_receive() -> + %% efficiency_guide.erl:194: Warning: INFO: not a selective receive, this is always fast + receive + Message -> handle_msg(Message) + end. + +%% DO NOT, unless Tag is known to be a suitable reference: see +%% cross_function_receive/0 further down. +selective_receive(Tag, Message) -> + %% efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference + receive + {Tag, Message} -> handle_msg(Message) + end. + +%% DO +optimized_receive(Process, Request) -> + %% efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position + MRef = monitor(process, Process), + Process ! {self(), MRef, Request}, + %% efficiency_guide.erl:208: Warning: OPTIMIZED: matches reference created by monitor/2 at efficiency_guide.erl:206 + receive + {MRef, Reply} -> + erlang:demonitor(MRef, [flush]), + handle_reply(Reply); + {'DOWN', MRef, _, _, Reason} -> + handle_error(Reason) + end. + +%% DO +cross_function_receive() -> + %% efficiency_guide.erl:218: Warning: OPTIMIZED: reference used to mark a message queue position + Ref = make_ref(), + %% efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218 + cross_function_receive(Ref). + +cross_function_receive(Ref) -> + %% efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1 + receive + {Ref, Message} -> handle_msg(Message) + end.]]></code> + </section> + </section> + + <section> <title>Constant Pool</title> <p>Constant Erlang terms (also called <em>literals</em>) are |