This is an intentionally rough guide to the relative costs of different calls. It is based on benchmark figures run on Solaris/Sparc:
Calls to local or external functions (foo(), m:foo()) are the fastest calls.
Calling or applying a fun (Fun(), apply(Fun, )) is about three times as expensive as calling a local function.
Applying an exported function (Mod:Name(), apply(Mod, Name, )) is about twice as expensive as calling a fun or about six times as expensive as calling a local function.
This is what I’ve got so far, but really, I don’t understand what an external function is (in the first bullet point).
Calls to local or external functions ( foo() , M.foo() ) are the fastest calls.
Calling or applying an anonymous function ( fun.() , apply(fun, []) ) is about three times as expensive as calling a local function.
Applying a public function from a module ( Mod.name() , apply(Mod, :name, []) ) is about twice as expensive as calling an anonymous function or about six times as expensive as calling a local function.
Right, so, wouldn’t that also be an external function? However, there are two bullet points (the first and third) from the original text that seem to me to be contradictory if this is the case:
Calls to local or EXTERNAL functions (foo(), m:foo()) are the fastest calls.
…
Applying an EXPORTED function (Mod:Name(), apply(Mod, Name, )) is about twice as expensive as calling a fun or about six times as expensive as calling a local function.
Maybe there is a difference here between m:foo() and Mod:Name() that I don’t understand? I feel like I’m missing something simple… Can somebody explain it like I’m 5?
You look at it in bad way … if exported means any public function (i.e. def my_func(…)) then it applies as same to ExternalModule.my_func() as __MODULE__.my_func() (__MODULE__ is compile-time alias for current module).
Anyway your confusion is about those calls:
my_func() - local function - looking only on such call we can’t say if it’s exported - could be, but don’t need to
MyModule.my_func() - if function is called with module and code compiles then that means such function is exported
apply(module, function, args) - this is run-time not optimized call - therefore few times more expensive, we can’t say if such function is exported or not even after compilation as calling here happens on run-time
Of course we always can look at those functions definitions.
From this all apply/2 or apply/3 calls are much slower, because of no compile-time optimizations.
Since compiler doesn’t know about this dependency, it cannot track it, and tools like xref cannot help either. At the same time, this provides higher level of decoupling between parts of the system. Thus, it’s not a way to structure your whole app, but it is useful for some subsystems inside a larger app.
Not sure how calls works internally in Erlang, but any atom (module names, function names etc.) is stored in atoms table:
An atom refers into an atom table, which also consumes memory. The atom text is stored once for each unique atom in this table. The atom table is not garbage-collected. By default, the maximum number of atoms is 1,048,576. This limit can be raised or lowered using the +t option.
Source: Erlang -- Advanced
Please look that you can use :erlang atom for pattern-matching, but also using it you can call :erlang module like:
:erlang.term_to_binary(term)
If atom is written directly (i.e. except call like for example Atom.to_string("runtime_atom")) the lookup happens on compile time.
Compiler in typical case works only on compile-time. Nothing stops you to compile/eval code, but generally all optimizations happen on compile time. As said apply/3 happens on runtime.
From erlang documentation:
Note: If the number of arguments are known at compile-time, the call is better written as Module:Function(Arg1, Arg2, …, ArgN).
Source: Erlang -- erlang
If compiler would internally rewrite the code then tools like xref would not have a problem. Please look that even module and function are able to be passed dynamically which makes impossible for compiler to know what would be called and with what number of calls. This call is known once the code is evaluated i.e. on runtime and that’s why it’s slower than code which directly points to specific function.
you export a function from a module to make it visible outside the module so it can be called externally
you make an external call to a function using mod:func(arg1, ...) which has been exported so it is visible outside its defining module
Note that if you make an external call to a function that function must be exported even if you are calling it from within the same module in which it is defined. So an external always goes “out” and then “in”. You typically don’t make external calls to functions in the same module as such calls can be affected by the code handling.