2
votes

I have a large number of processes that I need to keep track of in an ets set, and then randomly select single processes. So I created the set like this:

:ets.new(:pid_lookup, [:set, :protected, :named_table])

then for argument's sake let's just stick self() in it 1000 times:

Enum.map 1..1000, fn x -> :ets.insert(:pid_lookup, {x, self()}) end

Now I need to select one at random. I know I could just select a random one using :ets.lookup(:pid_lookup, :rand.uniform(1000)), but what if I don't know the size of the set (in the above case, 1000) in advance?

How do I find out the size of an ets set? And/or is there a better way to choose a random pid from an ets data structure?

2
You could use :ets.info(tab, :size) to get the size of the table. I don't really know the best option of grabbing a random element from it though. What is the usecase for grabbing a random pid from the set?Justin Wood
@JustinWood I'm just benchmarking genservers. I'm creating 1000 of them then I want each to send messages to another random one a bunch of times and see how long it takes with various message sizes. But as I create the Genservers they start so the other genservers might not be up yet when they start sending. So I need to know how many have been created in the initial stages of the benchmark.Thomas Browne
Well the 1000 is arbitrary of course. I'll check out the performance based on various numbers. I essentially want to get a mental map of how fast message passing is.Thomas Browne
If you just want to test message passing, why not spin up all the gen servers and then start sending messages?Justin Wood
Well I do want to know how to select stuff at random in an ets table for a bunch of statistics work I do anyway. But to answer your question directly, as I spin up the 1000 Genservers, the first ones will start sending before the last ones have spun up. Therefore, I can't just select a random number out of 1000. I need to know how many have been spun up, and for that I need to know what the size of the ETS table.Thomas Browne

2 Answers

1
votes
  • If keys are sequential number
tab = :ets.new(:tab, [])
Enum.each(1..1000, & :ets.insert(tab, {&1, :value}))
size = :ets.info(tab,  :size) 
# size = 1000
value_picked_randomly = :ets.lookup(tab, Enum.random(1..1000)) 

:ets.info(tab, :size) returns a size of a table; which is a number of records inserted on given table.

  • If you don't know that the keys are
first = :ets.first(tab)
:ets.lookup(tab, first)
func = fn key->
    if function_that_may_return_true() do
        key = case :ets.next(tab, key) do
         :'$end_of_table' -> throw :reached_end_of_table
         key -> func.(key)
        end
    else 
        :ets.lookup(tab, key)
    end
end
func.()

func will iterate over the ets table and returns a random value. This will be time consuming, so it will not be an ideal solution for tables with large number of records.

0
votes

As I understood from the comments, this is an XY Problem.

What you essentially need is to track down the changing list and pick up one of its elements randomly. ETS in general and :ets.set in particular are by no mean intended to be queried for size. They serve different purposes.

Spawn an Agent within your supervision tree, holding the list of PIDs of already started servers and use Kernel.length/1 to query its size, or even use Enum.random/1 if the list is not really huge (the latter traverses the whole enumerable to get a random element.)