[erlang-questions] Parameterized module idioms

Richard O'Keefe ok@REDACTED
Thu Apr 22 01:03:44 CEST 2010


There's quite a lot to reply to, and I don't have the time right now.
I'll just make a few points.

> Here there is a recompilation and selection of module. But if we  
> want to compute at startup time some value to serve as "parameter" ,  
> that value will have to be stored somewhere in a globally acessible  
> data structure like ets, to be consulted by my_config:dumplimit/1.  
> That can sometimes be slow.

If we want to compute at startup time some value to serve as a  
"parameter",
that value will have to be stored somewhere,
AND THAT SOMEWHERE CAN BE A MODULE.

There is no law that says a module can't be written by a program.

Here's an actual transcript.

Eshell V5.7.2  (abort with ^G)
1> c(reload).
{ok,reload}
2> reload:put(27).
{module,reloadable}
3> reload:get().
27
4> reload:put(42).
{module,reloadable}
5> reload:get().
42
6> reload:put(137).
{module,reloadable}
7> reload:get().
137
8> reload:put([<<1>>,<<2,3>>,<<4,5,6>>]).
{module,reloadable}
9> reload:get().
[<<1>>,<<2,3>>,<<4,5,6>>]

Now here's the proof-of-concept implementation.
Using compile:forms(..., [binary,...]) we can get the
same effect without touching the file system, and in
a "real" implementation that's what I'd do.

-module(reload).
-export([put/1,get/0]).

put(Datum) ->
     {ok,Device} = file:open("reloadable.erl", [write]),
     io:format(Device,
         "-module(reloadable).~n-export([datum/0]).~ndatum() ->~n     
~p .~n",
         [Datum]),
     ok = file:close(Device),
     compile:file("reloadable", []),
     code:purge(reloadable),
     code:load_file(reloadable).

get() ->
     reloadable:datum().

And yes, I do realise that what we have here is a global mutable
variable with an amazingly slow assignment statement,
but that's precisely what a changeable configuration parameter IS.

The overhead of of setting the parameter up (or changing it) is
moderately high (although using compile:forms(..., [binary,...])
would be more efficient as well as safer).  But the overhead of
*using* the parameter is the same as the overhead of any
cross-module function call.  And if that weren't tolerable, we
wouldn't be using Erlang.

And of course this can be generalised in a whole lot of ways.

>
> Now I remember that one of the "permissible" uses for the process  
> dictionary is to store "parameters" written once but never changed  
> later. This kind of use ties code with the process structure and the  
> use of get/1 can be slow.

Slow?  Time for some numbers.

6> getput:k(100000000).
[{variant,constant},{result,100000000},{time,530}]
7> getput:b(100000000).
[{variant,direct},{result,100000000},{time,1730}]
8> getput:t(100000000).
[{variant,dictionary},{result,100000000},{time,2290}]

Here's the code that produced those:

-module(getput).
-export([t/1, b/1, k/1]).

t(N) ->
     put(key, 1),
     {T0,_} = statistics(runtime),
     R = loop(N, 0),
     {T1,_} = statistics(runtime),
     [{variant,dictionary},{result,R}, {time,T1-T0}].

loop(0, R) -> R;
loop(N, R) -> loop(N-1, R+get(key)).

b(N) ->
     {T0,_} = statistics(runtime),
     R = loup(N, 0),
     {T1,_} = statistics(runtime),
     [{variant,direct},{result,R}, {time,T1-T0}].

loup(0, R) -> R;
loup(N, R) -> loup(N-1, R+(N div N)).

k(N) ->
     {T0,_} = statistics(runtime),
     R = lowp(N, 0),
     {T1,_} = statistics(runtime),
     [{variant,constant},{result,R}, {time,T1-T0}].

lowp(0, R) -> R;
lowp(N, R) -> lowp(N-1, R+1).

So
  - the loop that just  adds 1 takes  5.3 ns per iteration
  - the loop that adds N div N takes 17.3 ns per iteration
  - the loop that uses get()   takes 22.9 ns per iteration
We conclude that
  - N div N  takes 12 ns
  - get(key) takes 17.6 ns
and therefore
  EITHER my benchmark and interpretation are hopelessly fouled up
  OR using get/1 is NOT particularly slow.

To be honest, I incline to the former; how can looking something up
in a hash table be so good compared with an integer division?

While it may be true that get/1 _can_ be slow (I'd need to see the
numbers), you should never just _assume_ that get/1 is slow for
the use you intend to make of it.
>
> A quite common example would be several instances of a web server  
> together listening in different ports.

It's not clear why the port should be part of a web server's context
rather than part of its state, or why these instances need to be all
together in a single Erlang node (because if they aren't, module
parameters offer us no convenience), or why if they are all in a single
node "they" shouldn't be "it", a single system listening on several
ports and doing load balancing of some sort.
>



More information about the erlang-questions mailing list