What I don't understand about immutability is performance. How do these languages achieve small memory footprints and avoiding continuous allocations of new object versions, because a single property changed?
I mean, all I see are small scale examples where there are only a few properties. The production Rust code I did see, is passing copies of objects left and right. This makes me cringe at the inefficacy of such an approach.
Disclaimer: I have 0 experience in immutable languages, hence the question :)
1. Structural sharing - most data doesn't change so you create a copy that reuses much of the original structure
2. Garbage collection and lexical scopes - you clean up memory quickly and in batch
3. Compiler optimizations - you turn functional constructs into imperative ones that reuse and mutate memory at compile-time, where it is provably safe to do so
Just because something is semantically a copy doesn't mean that they will not be optimized out. Take a look at this example: https://godbolt.org/z/roxn43eMc
While create() semantically copies the struct Foo out to its caller, with optimizations on, the function isn't even invoked, let alone a copy be made.
That said, of course sometimes this optimization is missed, or it can't happen for some reason. But my point is just that some Rust code may look copy heavy but is not actually copy heavy.
Small example to show that performance can be great: Phoenix (the Rails-comparable web framework for Elixir) defaults to listing microseconds instead of milliseconds for response times.
Hmm, is rails really comparable to anything that's precompiled (genuine question, I don't mean this dismissively).
What I really meant was, as a backend engineer, I frequently deal with optimizations on too many object allocations and long running/too frequent GC cycles even without immutability built into the language.
On the Rust front, the problem is in small memory allocations, fragmented memory and then more calls to kernel to alloc.
> The production Rust code I did see, is passing copies of objects left and right.
You might be surprised how fast memcpy() is in practice on modern hardware. It's worth sitting down and writing a little C program that moves memory around and does some other stuff to get a feel for what the real world performance is like.
I too come from a time of worrying about memory access patterns and usage (pre virtual memory in hardware), so my initial reaction is like the parent comment, at least instinctively.
And yes, memcpy is fast, but I would not use a little program to convince myself. You will end up with stuff in CPU caches, etc, which will give you a very incorrect intuition.
Better to take a large program where there is a base factory and make some copies there or something and see how it affects things.
That said… for most businesses these days, developer time is more expensive than compute time, so if you’re not shipping an operating system or similar, it simply doesn’t matter.
And an optimizing compiler could do something like copy on write, and make much of the issue moot.
I had a brief period of time designing a simple CPU and it’s made everything since turn my stomach a little bit.
Memory is actually shared in Elixir until it's required to create new memory because of a change. So even if you have a big dictionary and something changes, part of the dictionary that remained the same will actually share memory until it's required to create new memory because of a change in that part of the dictionary.
So in effect, most immutable languages actually do copy by reference, it's just abstracted away for the programmer and you can reason about it as copy by value.
I can't find the article now but I remember reading about this and someone can probably link it.
Elixir/Erlang is not a fast language per se, in part because of this functional memory copying. Its apparent speed emerges when doing tasks that require high concurrency or parallelism.
If you are doing brute force calculations there are much faster languages out there.
I mean, all I see are small scale examples where there are only a few properties. The production Rust code I did see, is passing copies of objects left and right. This makes me cringe at the inefficacy of such an approach.
Disclaimer: I have 0 experience in immutable languages, hence the question :)