5 ms·
What I don't understand about immutability is performance. How do these languages achieve small memory footprints and avoiding continuous allocations of new obj
by prerok 2y ago
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 :)
- greener_grass 2y ago1. 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 Roc Lang might interest you: https://www.youtube.com/watch?v=vzfy4EKwG_Y https://www.youtube.com/watch?v=vzfy4EKwG_Y
- whateveracct 2y ago"Purely Functional Data Structure" by Chris Okasaki is a classic and imo a must-read data structures book. https://www.cs.cmu.edu/~rwh/students/okasaki.pdf https://www.cs.cmu.edu/~rwh/students/okasaki.pdf
- heeton 2y agoSmall 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.
- prerok 2y agoHmm, 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.
- samatman 2y agoRuby/Rails and Elixir/Phoenix both run on a garbage-collected virtual machine. I think that makes the comparison fair.
- munificent 2y ago> 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.
- sroussey 2y agoI 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.
- munificent 2y ago> 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. I did not say a program that works with little memory, I said a little program. :) void main() { malloc(10000000); } This is a very little program, but it's memory will certainly not all fit in the cache.
- steveklabnik 2y agoJust 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 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.
- prerok 2y agoThank you! Yeah, I will have to take a closer look at just why the copy elision isn't happening in the cases I looked at...
- POiNTx 2y agoMemory 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.
- kelseydh 2y agoElixir/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.