marcello

marcello

Hi everyone,

I’m just learning elixir with the help of “Elixir in Action” by Sasa Juric. In Chapter 5 “Concurrency primitives” there is something I don’t get. Maybe someone can help me?

It’s with regard to deep-copy data when sending data to another process.

“You may wonder about the purpose of shared-nothing concurrency. First, it simplifies the code of each individual process. Because processes don’t share memory, you don’t need complicated synchronization mechanisms such as locks and mutexes. Another benefit is overall stability: one process can’t compromise the memory of another.”

Why should there be a need for sync. mechanisms? Data is immutable, it can’t be mutated by another process. So compromising data shouldn’t be possible.
What am I missing?

Thanks, Marcello

Showing Posts 1 to 10

LostKobrakai

LostKobrakai

You’re missing that processes die and that makes their heap be cleaned up. Or in other words: When state is shared you need to make sure to only clean it up when nobody needs it anymore. By copying data there’s no need to track processes, which need access to a piece of data.

hubertlepicki

hubertlepicki

yes, precisely that. However, what @marcello says is true mostly for the small messages.

If you have a message consisting of binaries larger that 64 bytes (which is, relatively small), these will not be copied and instead will be allocated somewhere in designated space on the BEAM. So, if you are sending large blobs between processes these are not copied over.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Hey Marcello. To add on to what @LostKobrakai said, it also improves garbage collection performance while the processes are alive. Each process has it’s own heap that isn’t shared with anything, and this allows each process to be garbage collected independently of any other. This is an important feature for soft realtime systems, because it means that small processes communicating with clients won’t be paused for long periods just because some other process loaded a ton of data from something and will have a longer garbage collection.

marcello

marcello OP

Thanks for your replies! :slight_smile:

At first GC makes totally sense for me but I was wondering if that’s worth copying the data. Also as @hubertlepicki pointed out, binaries larger 64bytes will not be copied and kept on a special shared memory heap. So, one could also check, if some data is also used by another process, and if so, don’t clean that up.

But now I remember, that Erlang/OTP is designed for having thousands of processes - so checking on that may be a daunting task (also in the sense of performace and responsiveness). Maybe that is a valid reason?

So, GC as an arguement - I’m fine with that.
But compromising shared data between processes? Well, it’s immutable, isn’t it?
Synchronization? Again, it’s immutable…

Doesn’t make sense for me as arguements? Are these wrong or is there anything else?

Thanks for helping me!

LostKobrakai

LostKobrakai

GC is enough of a reason to need to copy data. The data can be as immutable as you like if you can’t be sure if the data is still available and not yet gone. Also e.g. the binary heap can quite quickly cause memory problems if a long running process is keeping (depending on the otp version even just a chunk of ) a large binary around and therefore that large binary cannot be cleaned up and stays around in memory.

peerreynders

peerreynders

I’m no BEAM engineer but some speculation…

Aside from the deallocation issues there could also be a re-location issue (defragmenting the heap). Multiple references to the same memory location will make difficult if not impossible to relocate the data and sooner or later you could run into heap fragmentation issues.

But as I said I don’t even know if the BEAM attempts to do this.

Another possibility is effects on memory latency. Now if all shared and non-shared values are stored exactly the same way there shouldn’t be any difference. But I wouldn’t be surprised if the compiler could more effectively optimize on non-shared values.

Aside: The Value of Values

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

This is what we mean by garbage collection. In order to do this globally you have to stop EVERY process when doing a garbage collection, like Java does. This is a problem.

Notably, the actual runtime will mutate underlying data in certain cases if it can be sure that the old data is no longer accessible in order to improve performance. These optimizations would become hard or impossible if data liveliness has to be tracked across all possible processes that can touch the data.

Related to what peerreynders said, having each process with it’s own heap ensures that when it’s time for a particular process to do work, all of it’s data is physically co-located in memory, which makes it easier for the CPU caches to work as designed. In fact many processes are small enough to fit inside the CPU caches entirely, which dramatically speeds up performance.

Really though, it’s all about garbage collection, either in the normal case or in the process death case. If processes can share data you either need to impose a ref counting penalty on ALL data or you need to impose “stop the world” garbage collection. Refcounting large binaries is an acceptable trade off for that data type, but not an acceptable trade off for others. Stop the world garbage collection isn’t acceptable for a soft real time system. Ergo, isolated process heaps.

marcello

marcello OP

Thanks everyone for explanations and spending your time on it!

I feel welcome here :slight_smile:

hubertlepicki

hubertlepicki

I did some tests on a real-life project and avoiding GC of kicing in during request is a big win. It actually happens a lot without much trying on the app I was testing on. Many times the request has been served without a single GC run, which means nothing has been stopped to clear memory and the user didn’t expect increased latency. Depending on your system the processes then either come back to the Ranch worker’s pool and can be GCed while idle or are dropped and restarted altogether nullifying the need for any GC.

rvirding

rvirding

Creator of Erlang

I think one thing to realise wrt gc is that if you share data between processes then when you do a gc you have to gc all the processes and the whole heap. This means that you need a real-time collector which complicates things. Running in multiple threads which the BEAM does complicates matters even more as you need to make it thread safe and/or pay a large cost in synchronisation. This is now done for the large binaries which results that sometimes it can take long time to reclaim their memory. Basically every process which has referenced the binary has to do a full gc before the binary can be reclaimed. This can take a comparatively long time and overflowing memory with unreclaimed large binaries is actually a problem.

So while not sharing data and copying data when sending messages is actually quite a good way of doing it even if it sounds wrong. Way back when I did some implementations of erlang with shared memory and real-time collectors and it is not trivial to get it right. Fun though, but not trivial.

In 22 there are some new special memory areas which are shared so some things are good and really fast but you also pay a heavy price. Check out atomics, counters and persistent_terms and you will see what I mean.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews