Browse Source

added performance tuning options

Ulf Wiger 14 years ago
parent
commit
669e1b2365
7 changed files with 149 additions and 26 deletions
  1. 2 0
      .gitignore
  2. 23 6
      doc/gproc.md
  3. 11 1
      doc/gproc_lib.md
  4. 29 11
      src/gproc.erl
  5. 2 1
      src/gproc_dist.erl
  6. 41 1
      src/gproc_lib.erl
  7. 41 6
      test/gproc_tests.erl

+ 2 - 0
.gitignore

@@ -2,3 +2,5 @@ current_counterexample.eqc
 deps/
 ebin/
 .eunit/
+*~
+*/*~

+ 23 - 6
doc/gproc.md

@@ -10,7 +10,8 @@ Module gproc
 * [Function Details](#functions)
 
 
-Extended process registry.
+Extended process registry  
+This module implements an extended process registry.
 
 
 
@@ -22,13 +23,32 @@ __Authors:__ Ulf Wiger ([`ulf.wiger@erlang-consulting.com`](mailto:ulf.wiger@erl
 
 
 
-This module implements an extended process registry
 
 
 For a detailed description, see
 [erlang07-wiger.pdf](erlang07-wiger.pdf).
 
-Type and scope for registration and lookup:
+
+
+<h2>Tuning Gproc performance</h2>
+
+
+
+
+
+Gproc relies on a central server and an ordered-set ets table.
+Effort is made to perform as much work as possible in the client without
+sacrificing consistency. A few things can be tuned by setting the following
+application environment variables in the top application of `gproc`
+(usually `gproc`):
+
+* `{ets_options, list()}` - Currently, the options `{write_concurrency, F}`
+and `{read_concurrency, F}` are allowed. The default is
+`[{write_concurrency, true}, {read_concurrency, true}]`
+* `{server_options, list()}` - These will be passed as spawn options when
+starting the `gproc` and `gproc_dist` servers. Default is `[]`. It is
+likely that `{priority, high | max}` and/or increasing `min_heap_size`
+will improve performance.
 
 
 
@@ -156,9 +176,6 @@ a = aggregate_counter
 <pre>unique_id() = {n | a, <a href="#type-scope">scope()</a>, any()}</pre>
 
 
-Type and scope for select(), qlc() and stepping:
-
-
 <h2><a name="index">Function Index</a></h2>
 
 

+ 11 - 1
doc/gproc_lib.md

@@ -28,7 +28,7 @@ For a detailed description, see gproc/doc/erlang07-wiger.pdf.
 
 
 
-<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#await-3">await/3</a></td><td></td></tr><tr><td valign="top"><a href="#do_set_counter_value-3">do_set_counter_value/3</a></td><td></td></tr><tr><td valign="top"><a href="#do_set_value-3">do_set_value/3</a></td><td></td></tr><tr><td valign="top"><a href="#ensure_monitor-2">ensure_monitor/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert_many-4">insert_many/4</a></td><td></td></tr><tr><td valign="top"><a href="#insert_reg-4">insert_reg/4</a></td><td></td></tr><tr><td valign="top"><a href="#remove_many-4">remove_many/4</a></td><td></td></tr><tr><td valign="top"><a href="#remove_reg-2">remove_reg/2</a></td><td></td></tr><tr><td valign="top"><a href="#update_aggr_counter-3">update_aggr_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#update_counter-3">update_counter/3</a></td><td></td></tr></table>
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#await-3">await/3</a></td><td></td></tr><tr><td valign="top"><a href="#do_set_counter_value-3">do_set_counter_value/3</a></td><td></td></tr><tr><td valign="top"><a href="#do_set_value-3">do_set_value/3</a></td><td></td></tr><tr><td valign="top"><a href="#ensure_monitor-2">ensure_monitor/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert_many-4">insert_many/4</a></td><td></td></tr><tr><td valign="top"><a href="#insert_reg-4">insert_reg/4</a></td><td></td></tr><tr><td valign="top"><a href="#remove_many-4">remove_many/4</a></td><td></td></tr><tr><td valign="top"><a href="#remove_reg-2">remove_reg/2</a></td><td></td></tr><tr><td valign="top"><a href="#update_aggr_counter-3">update_aggr_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#update_counter-3">update_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#valid_opts-2">valid_opts/2</a></td><td></td></tr></table>
 
 
 
@@ -140,3 +140,13 @@ For a detailed description, see gproc/doc/erlang07-wiger.pdf.
 
 `update_counter(Key, Incr, Pid) -> any()`
 
+<a name="valid_opts-2"></a>
+
+<h3>valid_opts/2</h3>
+
+
+
+
+
+`valid_opts(Type, Default) -> any()`
+

+ 29 - 11
src/gproc.erl

@@ -16,12 +16,29 @@
 %% @author Ulf Wiger <ulf.wiger@erlang-consulting.com>
 %%
 %% @doc Extended process registry
-%% <p>This module implements an extended process registry</p>
-%% <p>For a detailed description, see
-%% <a href="erlang07-wiger.pdf">erlang07-wiger.pdf</a>.</p>
+%% This module implements an extended process registry
 %%
-%% Type and scope for registration and lookup:
+%% For a detailed description, see
+%% <a href="erlang07-wiger.pdf">erlang07-wiger.pdf</a>.
 %%
+%% <h2>Tuning Gproc performance</h2>
+%%
+%% Gproc relies on a central server and an ordered-set ets table.
+%% Effort is made to perform as much work as possible in the client without
+%% sacrificing consistency. A few things can be tuned by setting the following
+%% application environment variables in the top application of `gproc'
+%% (usually `gproc'):
+%%
+%% * `{ets_options, list()}' - Currently, the options `{write_concurrency, F}'
+%%   and `{read_concurrency, F}' are allowed. The default is
+%%   `[{write_concurrency, true}, {read_concurrency, true}]'
+%% * `{server_options, list()}' - These will be passed as spawn options when 
+%%   starting the `gproc' and `gproc_dist' servers. Default is `[]'. It is 
+%%   likely that `{priority, high | max}' and/or increasing `min_heap_size'
+%%   will improve performance.
+%%
+%% @end
+
 %% @type type()  = n | p | c | a. n = name; p = property; c = counter;
 %%                                a = aggregate_counter
 %% @type scope() = l | g. l = local registration; g = global registration
@@ -29,8 +46,6 @@
 %% @type reg_id() = {type(), scope(), any()}.
 %% @type unique_id() = {n | a, scope(), any()}.
 %%
-%% Type and scope for select(), qlc() and stepping:
-%%
 %% @type sel_scope() = scope | all | global | local.
 %% @type sel_type() = type() | names | props | counters | aggr_counters.
 %% @type context() = {scope(), type()} | type(). {'all','all'} is the default
@@ -42,8 +57,8 @@
 %% @type pidpat() = pid() | sel_var().
 %% sel_var() = DollarVar | '_'.
 %% @type sel_pattern() = [{headpat(), Guards, Prod}].
-%% @type key()   = {type(), scope(), any()}
-%% @end
+%% @type key()   = {type(), scope(), any()}.
+
 -module(gproc).
 -behaviour(gen_server).
 
@@ -139,7 +154,9 @@
 %% @end
 start_link() ->
     _ = create_tabs(),
-    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+    SpawnOpts = gproc_lib:valid_opts(server_options, []),
+    gen_server:start_link({local, ?SERVER}, ?MODULE, [],
+			  [{spawn_opt, SpawnOpts}]).
 
 %% spec(Name::any()) -> true
 %%
@@ -1384,14 +1401,15 @@ pid_to_give_away_to({T,l,_} = Key) when T==n; T==a ->
     end.
 
 create_tabs() ->
+    Opts = gproc_lib:valid_opts(ets_options, [{write_concurrency,true},
+					      {read_concurrency, true}]),
     case ets:info(?TAB, name) of
         undefined ->
-            ets:new(?TAB, [ordered_set, public, named_table]);
+            ets:new(?TAB, [ordered_set, public, named_table | Opts]);
         _ ->
             ok
     end.
 
-
 %% @hidden
 init([]) ->
     set_monitors(),

+ 2 - 1
src/gproc_dist.erl

@@ -70,8 +70,9 @@ start_link(all) ->
 start_link(Nodes) when is_list(Nodes) ->
     start_link({Nodes, []});
 start_link({Nodes, Opts}) ->
+    SpawnOpts = gproc_lib:valid_opts(server_options, []),
     gen_leader:start_link(
-      ?SERVER, Nodes, Opts, ?MODULE, [], []).
+      ?SERVER, Nodes, Opts, ?MODULE, [], [{spawn_opt, SpawnOpts}]).
 
 %% ==========================================================
 %% API

+ 41 - 1
src/gproc_lib.erl

@@ -30,7 +30,8 @@
          remove_many/4,
          remove_reg/2,
          update_aggr_counter/3,
-         update_counter/3]).
+         update_counter/3,
+	 valid_opts/2]).
 
 -include("gproc.hrl").
 
@@ -261,3 +262,42 @@ scan_existing_counters(Ctxt, Name) ->
     Head = {{{c,Ctxt,Name},'_'},'_','$1'},
     Cs = ets:select(?TAB, [{Head, [], ['$1']}]),
     lists:sum(Cs).
+
+
+valid_opts(Type, Default) ->
+    Opts = get_app_env(Type, Default),
+    check_opts(Type, Opts).
+
+check_opts(Type, Opts) when is_list(Opts) ->
+    Check = check_option_f(Type),
+    lists:map(fun(X) ->
+		      case Check(X) of
+			  true -> X;
+			  false ->
+			      erlang:error({illegal_option, X}, [Type, Opts])
+		      end
+	      end, Opts);
+check_opts(Type, Other) ->
+    erlang:error(invalid_options, [Type, Other]).
+
+check_option_f(ets_options)    -> fun check_ets_option/1;
+check_option_f(server_options) -> fun check_server_option/1.
+
+check_ets_option({read_concurrency , B}) -> is_boolean(B);
+check_ets_option({write_concurrency, B}) -> is_boolean(B);
+check_ets_option(_) -> false.
+
+check_server_option({priority, P}) ->
+    %% Forbid setting priority to 'low' since that would
+    %% surely cause problems. Unsure about 'max'...
+    lists:member(P, [normal, high, max]);
+check_server_option(_) ->
+    %% assume it's a valid spawn option
+    true.
+
+get_app_env(Key, Default) ->
+    case application:get_env(Key) of
+	undefined       -> Default;
+	{ok, undefined} -> Default;
+	{ok, Value}     -> Value
+    end.

+ 41 - 6
test/gproc_tests.erl

@@ -22,6 +22,42 @@
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("stdlib/include/qlc.hrl").
 
+conf_test_() ->
+    {foreach,
+     fun() ->
+	     application:unload(gproc)
+     end,
+     fun(_) ->
+	     application:stop(gproc)
+     end,
+     [?_test(t_server_opts()),
+      ?_test(t_ets_opts())]}.
+
+t_server_opts() ->
+    H = 10000,
+    application:set_env(gproc, server_options, [{min_heap_size, H}]),
+    ?assert(ok == application:start(gproc)),
+    {min_heap_size, H1} = process_info(whereis(gproc), min_heap_size),
+    ?assert(is_integer(H1) andalso H1 > H).
+
+t_ets_opts() ->
+    %% Cannot inspect the write_concurrency attribute on an ets table in
+    %% any easy way, so trace on the ets:new/2 call and check the arguments.
+    application:set_env(gproc, ets_options, [{write_concurrency, false}]),
+    erlang:trace_pattern({ets,new, 2}, [{[gproc,'_'],[],[]}], [global]),
+    erlang:trace(new, true, [call]),
+    ?assert(ok == application:start(gproc)),
+    erlang:trace(new, false, [call]),
+    receive
+	{trace,_,call,{ets,new,[gproc,Opts]}} ->
+	    ?assertMatch({write_concurrency, false},
+			 lists:keyfind(write_concurrency,1,Opts))
+    after 3000 ->
+	    error(timeout)
+    end.
+
+
+
 reg_test_() ->
     {setup,
      fun() ->
@@ -75,8 +111,6 @@ t_simple_reg() ->
     ?assert(gproc:unreg({n,l,name}) =:= true),
     ?assert(gproc:where({n,l,name}) =:= undefined).
 
-
-                       
 t_simple_prop() ->
     ?assert(gproc:reg({p,l,prop}) =:= true),
     ?assert(t_other_proc(fun() ->
@@ -96,7 +130,10 @@ t_other_proc(F) ->
 t_await() ->
     Me = self(),
     {_Pid,Ref} = spawn_monitor(
-                   fun() -> exit(?assert(gproc:await({n,l,t_await}) =:= {Me,val})) end),
+                   fun() ->
+			   exit(?assert(
+				   gproc:await({n,l,t_await}) =:= {Me,val}))
+		   end),
     ?assert(gproc:reg({n,l,t_await},val) =:= true),
     receive
         {'DOWN', Ref, _, _, R} ->
@@ -109,7 +146,6 @@ t_is_clean() ->
     sys:get_status(gproc), % in order to synch
     T = ets:tab2list(gproc),
     ?assert(T =:= []).
-                                        
 
 t_simple_mreg() ->
     P = self(),
@@ -356,7 +392,6 @@ t_loop() ->
 	{From, die} ->
 	    From ! {self(), ok}
     end.
-    
 
 t_call(P, Msg) ->
     P ! {self(), Msg},
@@ -371,7 +406,7 @@ spawn_helper() ->
 		      ?assert(gproc:reg({n,l,self()}) =:= true),
 		      Ref = erlang:monitor(process, Parent),
 		      Parent ! {ok,self()},
-		      receive 
+		      receive
 			  {'DOWN', Ref, _, _, _} ->
 			      ok
 		      end