earth10

earth10

Understanding Elixir/Phoenix performance

Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone.

What strikes me is that almost every task seems several times slower programmed in Elixir than with Python or Perl. Examples are: traversing a directory and read file modification times, read a CSV file line by line and do some basic processing with them etc.

Despite this, Elixir and Phoenix show excellent performances when compared to web frameworks written in other languages.

If I understand correctly, Elixir “worse” raw computational speed is more than balanced from its superiority in concurrency. Oversimplifying: Elixir can be 10 times slower than language X but if it’s 1000 times better in concurrency, it will shine for high traffic websites.

But then Go comes into play: with excellent raw speed and excellent concurrency too, it should outperform Elixir easily. Which according to my reading doesn’t happen: it may be faster but not by the large amount I would think.

Can somebody help me to understand how it is possible? I’m not asking for low-level explanations, only some pointer for further reading. (Maybe I should just be happy with the end result but I like to understand why things work in a given way :slight_smile: )

Thanks!

First Post!

idi527

idi527

:waving_hand:

What strikes me is that almost every task seems several times slower programmed in Elixir than with Python or Perl. Examples are: traversing a directory and read file modification times, read a CSV file line by line and do some basic processing with them etc.

If possible I’d first like to take a look at your elixir code. Sometimes there are ways to improve it a bit.

Most Liked

josevalim

josevalim

Creator of Elixir

Doing file traversals is generally not going to be as efficient in Elixir/Erlang as in other languages. I will explain why.

When you call File.open/2 in Elixir, it doesn’t return a file handler. It returns a process (a lightweight thread of execution) that contains the file handler. But the file handler itself is not even a direct file handler, as you would get in C, but it is an instance of a linkedin driver, which is a piece of code that runs isolated in the VM, that then talks to the file handler.

You may be wondering: why all of this indirection then?

The reason why File.open/2 returns a process is because we can then pass this process around nodes and do file writes across nodes. So for example, I can open up a file on node A, pass that reference to node B, and node B can read/write to that file as if it was in node B, but everything is actually happening in node A. So the reason why we do this is because we favor distribution over raw performance.

What about the linked driver thing though? There are two reasons. First of all, let’s remember that those kind of operations need to be implemented in C or a low-level language for syscalls. And while Erlang provides interoperability with C code, in earlier versions, it was not possible to do an I/O based operation from within the C code. If you did that, you could mess up with the Erlang schedulers that are responsible for concurrency. The second reason is that, if you have C code and there is a bug in that C code, then it can cause a segmentation fault and bring the whole system down, so we prefer to keep our systems running. That led the code to be put in those linked drivers.

Of course all of this adds overhead but the reason we are fine with it is because for our use cases it is most likely that you will find yourself passing a file between nodes than traversing directories as fast as possible, so we focus on the former.

The situation has improved in the latest Erlang/OTP 21 release because the VM added the ability to run I/O blocking C code with something called dirty NIFs, so they recently removed the linked drivers for file operations and that improved performance. But still, most calls in the File module is going through processes and what not. You can actually bypass this process architecture, usually by invoking the :prim_file module or passing a [:raw] option to the File module operations and that typically improves things.

But in a nutshell that’s why it won’t be as fast, because there are many cases where we prefer to focus on features such as distribution and fault tolerance than raw performance.

Btw, regarding CSV processing, did you try the nimble_csv library?

sasajuric

sasajuric

Author of Elixir In Action

In my experience, most of such synthetic challenges are significantly skewed due to a suboptimal algorithm. In this example, we can significantly improve the running time by changiing the algorithm. Here’s a demo on 40th fibonacci:

defmodule NaiveFib do
  def of(0), do: 0
  def of(1), do: 1
  def of(n), do: of(n - 1) + of(n - 2)
end

{time, res} = :timer.tc(fn -> NaiveFib.of(40) end)
IO.puts("Naive fib returned #{res} in #{time} microseconds")

defmodule BetterFib do
  def of(0), do: 0
  def of(1), do: 1
  def of(n), do: of(0, 1, 2, n)

  defp of(x, y, n, n), do: x + y
  defp of(x, y, m, n), do: of(y, x + y, m + 1, n)
end

{time, res} = :timer.tc(fn -> BetterFib.of(40) end)

IO.puts("Better fib returned #{res} in #{time} microseconds")

Output:

Naive fib returned 102334155 in 2776981 microseconds
Better fib returned 102334155 in 0 microseconds

As we can see, the algorithmical optimization leads to an almost instantaneous computation. Of course, the equivalent go version would still be faster, but at this speed the difference doesn’t matter. So more generally, an algorithmical optimization can do wonders for performance. If that still doesn’t cut it, it’s perhaps time to consider a faster language. If that is the case, I’d probably go for C or Rust instead of go. But in my experience, more often than not, the challenge can be solved within a reasonable time using a proper algorithmical/technical combo :slight_smile:

michalmuskala

michalmuskala

Web servers are fast in Elixir because web servers don’t do anything most of the time - most of the time they are just waiting. Either for request data or for database, etc. Elixir/Erlang are excellent at finding things to do when one of the processes doesn’t do anything, which makes them generally fast at web servers.

There’s also a question of algorithms. If you use an algorithm designed with mutable data structures in mind, it will be unavoidably slower when used with immutable data structures - on the other hand, there are some algorithms designed for immutable data structures and different, more specialised structures that can shine in some cases.

Finally, there’s the matter of the VM. BEAM is just a very well implemented and a very efficient machine. The runtime system responsible for IO interaction, scheduling and similar things have been optimised over the years. Yes, it does not have a JIT, but the normal emulator is quite fast compared to other VMs. It’s also one of the few register-based VMs in the wide usage, and register VMs generally tend to be faster than the more popular and simpler stack-based VMs.

17
Post #7

Last Post!

danyalmh

danyalmh

Must see performance in real world .

If you want serving to Over 300K request per second in a single Machine, and this serving is real-time Searching or transaction processing with Database

Elixir
Phoenix
Mnesia ( in-memory, extermely Fast DB )

Is single Solution in The world :wink:

Where Next?

Popular in Questions Top

New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36689 110
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54996 245
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 40082 209
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

We're in Beta

About us Mission Statement