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: https://github.com/KieranP/Game-Of-Life-Implementations/tree/master/elixir

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

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Are protocols even consolidated if he’s not using a mix project? I wonder if that’s a factor here too.

Where Next?

Popular in Questions Top

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
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
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
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
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
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
jc00ke
Expanding on this topic: https://forum.elixirforum.net/t/map-typespec-question/19217 Let’s say I have a map with required and optional k...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement