Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the differences between ETS, persistent_term and process dictionaries?

I know there are (at least) three ways of having a mutable state in Erlang:

  • ETS tables
  • persistent_term
  • Process dictionaries

The basic usage of them looks very similar to me:

% ETS
1> ets:new(table1, [named_table]).
2> ets:insert(table1, {thing}).
3> ets:lookup(table1, thing).
[{thing}]

% persistent_term
1> persistent_term:put(table2, thing).
2> persistent_term:get(table2).
thing

% Process dictionary
1> put(table3, thing).
2> get(table3).       
thing

What are the differences and pros/cons of using one over another?

I see that ETS behaves more like a map, but how does it differ from keeping maps in persistent_terms or process dictionaries?

like image 844
radrow Avatar asked Oct 26 '25 03:10

radrow


2 Answers

persistent_term and ets expose a similar API, but they are different, I'll quote the manual:

Persistent terms is an advanced feature and is not a general replacement for ETS tables.

The differences lie in where are the terms stored and what happens on updates, ETS terms are copied to the process heap on read, where persistent_term terms are only referenced. This causes that when a item from the persistent_term is updated, all the processes that had a copy (reference) of this term need to actually copy it to their heap to continue using it.

With huge terms, referencing them instead of copying them saves a lot of time, but the penalty of the updates is harsh.

Also, persistent_term is only a map, whereas ETS may be a set, ordered set, bag or dupicate bag, ETS provide select and match semanticts too.

The persistent_term is used as a optimized replacement for a very specific use case the ETS were being used to.

Regarding the process dctionary, it's a local #{} inside the process, always available. ETS and persistent_term are available for every process, the local process dictionary is different for each process. Be very careful when using the process dictionary, as it hurts legibility.

like image 77
José M Avatar answered Oct 28 '25 03:10

José M


Some complimentary information to @José answer:

process_dictionary is local, it dies with the process, cannot be accessed from external process

persistent_element is global, it dies only with the node. It can be accessed by any process, without control.

ETS is owned by a process, it can be shared by different processes, it dies with the owner, the ownership can be given away to another process, it offers some access control (public, private, protected) and different organization types and accesses.

a short session in the shell shows (most) of these differences.

12> persistent_term:put(global,value1).
ok
13> put(local,value2). % with process_dictionary, put returns the previous value associated to the key
undefined
14> ets:new(my_ets,[named_table,public]). % create a public table
my_ets
15> ets:insert(my_ets,{shared,value3,other_values}).
true
16> persistent_term:get(global).  % check that everything is stored                  
value1
17> get(local).                 
value2
18> ets:lookup(my_ets,shared).
[{shared,value3,other_values}]
19> ets:lookup_element(my_ets,shared,2). % a small example of ETS extended capabilities
value3
20> F1 = fun(From) -> From ! persistent_term:get(global) end. % prepare the same functions to be executed from external process
#Fun<erl_eval.44.97283095>
21> 
21> F2 = fun(From) -> From ! get(local) end.                  
#Fun<erl_eval.44.97283095>
22> F3 = fun(From) -> From ! ets:lookup(my_ets,shared) end.
#Fun<erl_eval.44.97283095>
23> Me = self().
<0.19320.1>
24> spawn(fun() -> F1(Me) end).
<0.12906.2>
25> flush(). % persistent_term are global
Shell got value1
ok
26> spawn(fun() -> F2(Me) end).
<0.13701.2>
27> flush(). % prrocess_dictionary is local                  
Shell got undefined
ok
28> spawn(fun() -> F3(Me) end).
<0.13968.2>
29> flush().  % ETS can be shared                 
Shell got [{shared,value3,other_values}]
ok
30> 1/0. % create an exception so the shell dies and is restarted by its supervisor
** exception error: an error occurred when evaluating an arithmetic expression
     in operator  '/'/2
        called as 1 / 0
31> Me = self().  % the shell's Pid changed             
** exception error: no match of right hand side value <0.14499.2>
32> persistent_term:get(global).    % persistent are still there                          
value1
33> get(local).  % Oooops! the process dictionary is still there too. Warning, this is a side effect of the shell implementation                                             
value2
34> ets:lookup(my_ets,shared).  % my_ets does not exist anymore                      
** exception error: bad argument
     in function  ets:lookup/2
        called as ets:lookup(my_ets,shared)
35> 
like image 30
Pascal Avatar answered Oct 28 '25 04:10

Pascal



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!