k776

k776

Slow performance of Game of Life Implementation - suggestions?

Hello. As a side project, I have created various implementations of Conways Game of Life in over 20 languages. Over this past weekend, I set out to create a version with Elixir. This was my first time using a purely functional AND immutable language, so it took a little bit to get my head around the idea that I couldn’t update a struct value without having to return the updated parent struct it was nested in all the way back up to the top stack and then back down the method chain again.

Anyway, I got there in the end and this is the final result: Game-Of-Life-Implementations/elixir at master · KieranP/Game-Of-Life-Implementations · GitHub

However, the performance is really really bad. It currently ranks the worst of all the implementations I have done, sitting at ~3.7x slower than the next slowest, and ~7.9x slower than the latest CRuby.

I have traced the slowness down to a Map.get call I use to fetch a world cell by specific coordinates and check its alive state (see world.ex, cell_at method). Each world tick, this method is called 8 times for every cell in the world (there are 6000 cells, therefore some 48,000 calls). (see world.ex, alive_neighbours_around method).

If I change alive_neighbours_around to return 0 by default, thus bypassing the cell_at calls, avg tick time drops from 30.61ms per tick to around 2.8ms per tick, which takes it from being ~7.9x slower than CRuby to being ~1.4x faster. So it appears than the cell_at method, which only calls Map.get, accounts for 90.85% of the runtime.

The reason for the slowness compared to other languages is that other languages allow me to reference/point to the world cell from another cell (i.e. a world has many cells, and a cell has many neighbours, which are references/pointers to the world cells). In other languages, when I loop over a cells neighbours, they can fetch the current alive value from the world cell through that reference. In Elixir however, there doesn’t appear to be any concept of references, so a cells neighbours are just coordinates, and to get the current cell value, I have to make a cell_at call with those coordinates to get the latest data from the world cell.

So I understand the cause of the problem. I guess I’m looking for some advice on how I might speed up the implementation without deviating drastically from the other implementations. I guess what I need to know is:

  • Are there actually memory references in Elixir that I’ve just missed? Can a cell neighbour point to a world cell in order to fetch its value?
  • If there are not references/pointers in Elixir, is there a better/faster way of storing structs by a key other than via a Map?

Besides these things, I’d also be keen to hear how the general syntax is and whether there are general things I could do better (e.g. is building a string using for loops and reduce the right approach, see world.ex render).

Many thanks for any suggestions.

Most Liked

al2o3cr

al2o3cr

+1 to @hst337’s suggestion of using an {x, y} tuple as a key instead of interpolating a string.

Making that change alone drops the “world tick” time from ~55ms to ~15ms on my local machine.

The other major performance problem is too much garbage - put_in is powerful, but using it inside of loops like this may not be great for performance. The Ruby version avoids this by mutating cells in place, but you’ll need to pick an alternate approach in Elixir:

  • keep per-tick data like neighbors separate from cells and apply the alive/dead calculation in one pass over cells
  • use an ETS table for cells
  • make add_cell smarter and pre-compute neighbors when a cell’s neighbors change instead of every tick

The ultimate motivation behind all of these approaches is to do as little work as possible for “static” cells.

hst337

hst337

  1. Map keys do not have to be strings, you can just use {x, y} as key instead of "#{x}-#{y}"
  2. You don’t need to store world as a Map, you can just store it as an array, since it is fixed size. And access it like def cell_at(%World{width: w, array: a}, x, y), do: :array.get(x + y * w, a)
  3. I’d suggest storing cell data in a tuple. If you want names, you can just use Record.

NITs: Elixir has functions, not methods. Use mix new to create and set up projects. Elixir doesn’t have references, since they can’t be immutably tracked in Elixir’s semantics.

tcoopman

tcoopman

Another tip: only store live cells. Often a huge percentage of cells is dead, so storing those adds some overhead. Probably mostly memory though. But it might add some performance as well.
That also means that you don’t need to do a check if the cell is alive or not

Where Next?

Popular in Questions Top

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
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
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
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
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
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement