Fl4m3Ph03n1x

Fl4m3Ph03n1x

Module with too many functions exausts erlang atoms

Background

We are currently testing a module with 10 millions functions. This is an automatically generated module that we (pesky humans) can’t touch. Ever.

Problem

Upon compiling said module BEAM blows saying that we have gone over the atoms limit for erlang. This is surprising. Following is the code sample used to generate the automated module (here simplified), which will cause you the same problems:

defmodule PocManyClauses do
  @list (1..10000000) |> Enum.map(fn n -> :"fn_#{n}" end)

  Enum.map(@list, fn n ->
    def unquote(n)(), do: unquote(n)
  end)

end

Questions

Are function names considered atoms in erlang?
Or is the example we are using malformed?

Most Liked

ferd

ferd

Author of Property-Based Testing with PropEr, LYSE, & Erlang in Anger

I don’t really know how to answer to this in a reasonable way, but why would modules with 10,000,000 functions be the appropriate tool for the job?

Particularly if you need special build machines just to accommodate potentially compiling the thing and keep running into limitations of the runtime environment you’re using, I would be led to believe that you need a different tool than the one that keeps breaking and failing to do what you want it to do.

Is there a different approach that you could take, or maybe you found a problem that’s just not a good fit for the VM? This sounds like a nightmare.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

This line here clouds what you’re trying to prove. This first generates a list of 10 million atoms. This will blow out the atom table long before you try to actually make any functions.

iex(1)> defmodule PocManyClauses do
...(1)>   @list (1..10000000) |> Enum.map(fn n -> :"fn_#{n}" end)
...(1)> end
no more index entries in atom_tab (max=1048576)

Crash dump is being written to: erl_crash.dump...done

Regardless, it still probably isn’t possible to generate a function with 10 million clauses even if you could get past the atom thing. In Absinthe we generate a LOT of functions and compile time starts becoming an issue orders of magnitude below 10 million.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Is this true? The O() behaviour of pattern matching depends on the complexity and kind of pattern. For integer literals and atom literals (which boil down to integers) it’s my understanding that the beam can produce O(1) lookup tables. You’re trying to generate range check clauses though and I’m pretty sure that the theoretical best for that is O(log(n)).

More to the point, the O here doesn’t really matter cause you have a fixed size lookup space. What you need to determine is how fast a given lookup is, and how many lookups the system can handle concurrently. If that meets your needs then the O doesn’t really matter. If it’s too slow then yeah, looking for something with a better O can be a good guide, but I’d make sure your problem space actually permits O(1).

sasajuric

sasajuric

Author of Elixir In Action

The default limit for atoms is 1,048,576 (see here). You could increase it by providing the +t n option, e.g. iex --erl "+t 20000000".

However, 10 million functions seem quite extreme, so not sure if you’ll bump into some other problems. Perhaps considering other options, such as multiclauses, or statically generated maps, or some such would work better. In any case, I’m curious to read more about your experiences with it.

sasajuric

sasajuric

Author of Elixir In Action

If you’re mapping a value to another value, you can consider building a compile-time map. Here’s a sketch:

defmodule Test do
  @squares 1..10_000_000
           |> Enum.map(&{&1, &1 * &1})
           |> Map.new()

  def square(x), do: Map.fetch!(@squares, x)
end

Testing on my machine, it takes about 2 minutes to compile this thing. Peak memory usage during compilation is about 10 gb, and the generated beam is about 200 mb, which is also roughly the memory overhead when the module is loaded at runtime. Invoking Test.square/1 takes around 5 microseconds (after the module has been loaded). This huge map should reside in a constant pool, so you should be able to safely access it from multiple processes without creating extra copies of it.

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
gonzofish
I’m currently trying to understand how to join three tables using Ecto. All the examples I’ve seen use 2, so maybe I’m just missing somet...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New

Other popular topics Top

lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement