[erlang-questions] atomic ets access sequences

Ulf Wiger (TN/EAB) <>
Sun May 6 15:04:20 CEST 2007

After bashing ets, I will now venture to try to 
make it even dirtier than it already is. (:

I've put together a BIF in erl_db.c (the ets "module"), 
called seq/3. Rather than posting the C code (which is 
certainly horrific, as I am no C programmer; it is 
also pretty bulky), I've written a reasonably 
equivalent Erlang function (attached).

The main difference between the two, except for the 
C version being faster, is that the C version is 
atomic. It allows for some rudimentary lookups and 
selects, followed by updates (insert  <<eets.erl>> and delete), 
provided that the preconditions are met.

I've so far only written one simple example: 
which increments a counter object if it exists, 
or creates it with a reasonable default (Incr), 
if it doesn't. This is what it looks like - ugly,
but I think the main ugliness lies in the 
unavoidable select patterns:

increment(Tab, Key, Incr) ->
  Pat = [{ {Key, '$1'}, [],
         [{{ Key, {'+','$1',Incr} }}] }],
  seq(Tab, [{1, bind, Pat}],
      [{foreach, 1, insert},
       {if_nil,  1, [{insert, {Key, Incr}}]}]).

(Explanation: The call is seq(Tab, Preconditions, Actions)
The {1, bind, Pat} calls ets:select(Tab, Pat) and saves the 
result in a numbered slot (there are 10 of them). 
The foreach instruction takes each object in slot 1 
and - in this case - calls ets:insert(Tab, Obj); the 
if_nil instruction is run if slot 1 contains []. It then
initializes the counter object.)

Here's where you get to provide feedback, shout heresy!! 
or perhaps ask to get the C code an help whip it into 
shape. Some of you may have good suggestions on how to 
make it more intuitive. I've tried to limit myself to
actions that are unlikely to crash, once the preconditions
are met.

The biggest gripe of my own is that the function cannot
be made to return anything other than true or false, but 
I've saturated my quota for prototyping in C for now.

One use I had in mind is the extended process registry,
which is currently dependent on sending all registrations
to a gen_server. Using this function, registering a unique
name could look like this:

reg(Name) ->
  Pat = [{{{Name,rn,'_'}}, [], [true]}]}]
      [{0, select_count, Pat],
      [{insert, [{{Name,rn,self()}},

(The select_count pattern returns 1 if there is 
a reverse mapping of the name Name to any pid.
We perform the insert action only if the 
select_count result is 0.)

In a simple test, this turned out to be less than
twice as fast as my Jungerl version (proc). This
is a bit disappointing, but latency should be much
improved, and a registry basically needs to be as 
lightweight as it can possibly be.

Another obvious use would be a sort of transaction
memory, where an object is first read, then modified,
and later committed, assuming that the stored object
hasn't been changed by someone else:

commit(Tab, NewObj, OldObj) ->
  Pat = [{OldObj,[],[true]}],
  seq(Tab, [{0,select_count,Pat}],
      [{insert, NewObj}]).

Anyway, there it is. Comments?

Ulf W
-------------- next part --------------
A non-text attachment was scrubbed...
Name: eets.erl
Type: application/octet-stream
Size: 4002 bytes
Desc: eets.erl
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20070506/404e682a/attachment.obj>

More information about the erlang-questions mailing list