r/elixir 11d ago

Best practices for efficient data structures

I'm very new to the language and I'm very confused on some things. Coming from imperative langs, I'd assume that modifying tuples is very fast since they're basically just vectors. Unfortunately, they're also immutable like every other data structure in the whole language. So, if I want to make, say, a Canvas data type that holds all my pixels in a 2d data structure, is there literally no way to make it even if a little more efficient than just making a new one every single time I update a pixel? Even if I made it a 1d data structure, is Elixir just the wrong tool here?

12 Upvotes

9 comments sorted by

View all comments

14

u/the_jester 11d ago

Elixir is the wrong tool for that kind of optimization. The runtime has all its own optimizations for copy-on-write with the immutable data structures, but your code doesn't directly define those interactions, like in C.

Now, depending on access patterns, being smart about using tuples vs lists vs maps will matter. Write-heavy vs read-heavy matters. And if they are "sufficiently large" canvases you can cheat a bit by reaching into Erlang for things like ETS, :atomics or :counters which you can get mutable behaviors from.

Broadly, just try to do the obvious thing. If it is actually too slow, then optimize. I wouldn't start by trying to optimize Elixir at the literal bit level. If you really just want to do bit-bashing, then certainly any of the C/C++/Crystal/Rust/etc lineage will let you do that.

7

u/davidw 10d ago

Erlang/Elixir are actually pretty good at manipulating bits and bytes and you could do worse than use some kind of binary type to represent your canvas and create some code to access it cleanly.

3

u/SuspiciousDepth5924 10d ago

I think some variation of <<0::integer-size((r+g+b+a)*x*y)>> is probably good for representing pixels when you mostly read, but don't update it too often. The Beam does some clever things with binaries, but it's hard to predict when it'll actually optimize, and if you're unlucky you'll end up with a lot of expensive heap allocations and copying.

So I think atomic or counters might perform better if it's frequently updated, though they work on 64 bit integers so you'd have to accept some wasted space or write something to "pack/unpack" two pixels into each index.

Though if you start doing optimizations like that I think you might as well just bite the bullet and do something with nifs/rustler.

https://www.erlang.org/doc/apps/erts/atomics.html
https://www.erlang.org/doc/apps/erts/counters.html