This happens only with piles of Java crap (gigabytes of jars). With [mostly] functional Erlang, Common Lisp, Haskell everything is fine.
This happens only with piles of Java crap (gigabytes of jars). With [mostly] functional Erlang, Common Lisp, Haskell everything is fine.
Java code tends to be object-oriented and create a lot of objects. Functional code can create significantly fewer objects, so even though the Java world has the best GCs (like Azul C4) those other languages typically give their GCs less work to do. That also means that if you write code that does give a lot of work to the GC in a functional language it can certainly perform very poorly as well.
Erlang is probably different, afaik creating lots of (lightweight) processes is frequent in Erlang. I don't know how the Erlang GC works so I can't really comment on that.
If you're interested, here are a few helpful blogposts on the subject:
- This is a pretty short and readable post, though it's a bit old (2008): "Garbage Collection in Erlang" [0]
- A more detailed (and recent -- 2015) blogpost: "Erlang Garbage Collection Details and Why It Matters" [1].
- And this is the most up-to-date description, from April 2016 concerning Erlang R19, by Lukas Larsson, who works on the Erlang VM -- it is long, but well-written and has nice illustrations [2].
[0] http://prog21.dadgum.com/16.html
[1] https://hamidreza-s.github.io/erlang%20garbage%20collection%...
[2] https://www.erlang-solutions.com/blog/erlang-19-0-garbage-co...
In order to explain current default Erlang’s GC mechanism concisely we can say; it is a Generational Copying garbage collection that runs inside each Erlang process private heap independently, and also a Reference Counting garbage collection occurs for global shared heap.
(To expand on that a little: each process has its own copy of everything that it needs -- if it needs something from another process, it'll get a copy in a message sent to its mailbox, so: no shared memory, links, etc.
This encapsulation means that when a process terminates, all its memory is instantly reclaimed -- no GC is required, since no other process can possibly be referencing any data in the process that's going away.
And, while a process is alive, there's per-process generational-copying GC, as mentioned.
However, for pragmatic reasons, items > 64 bytes are stored in a global heap and references to those items are passed used (i.e. to avoid sending large things around) -- that's where the ref-counting GC comes in).
Another principal design decision, which complements share-nothing architecture, is a pure functional language with single "assignment". This greatly simplified runtime and VM itself which leads to more controlled and predictable behavior, suitable for soft-realtime systems.
The concept of drivers is another one, but it is irrelevant to this discussion.
Many functional languages allow imperative parts of course, but even in that case how is it less than the object oriented approach rather than just the same?
The majority of functional languages support value types (in the stack/global sense), which many OO languages that follow Smalltalk don't. Also escape analysis tends to be relatively weak in most JVM implementations (maybe Graal is the exception here).
Immutable data makes it easier to collect garbage when moving living data between nurseries, thus leading to shorter pauses as in imperative languages.
EDIT: Updated the sentence about escape analysis.
(Yes, this means that, from a GC implementor's POV, a lazy language like Haskell mutates like crazy.)
Is moving a burden from the runtime to the programmer a good idea? Generally, no. There might be something about this particular case that makes it an exception to the general rule, but the point seems very far from proven.
I haven't reached the stage where I can write performant Rust yet since I've only recently started to truly grok the borrow checker but I can see how longer term it will be easier to figure out how to make my code performant since the all the information required is front and center. It will be interesting to see whether that promise holds for rust code as the community matures.
From a Rust POV (a useful one, even if you're using a higher-level language), when you use a garbage collector, technically everything is owned by the garbage collector, and merely borrowed by you. That's why in most languages you can get away with making shallow copies everywhere, even when Rust would tell you that a `.clone()` is in order.
(0) If the object has, say, just two or three fields (which isn't uncommon in functional languages, since they really tend to decompose everything in a piecemeal fashion), this is no big deal.
(1) If a functional language has substructural types (which admittedly most don't), the compiler can even tell, purely from type information, when an object won't be used anymore and can be overwritten anyway, as though you had used imperative assignment all along.
And muuuch better type safety. You can only mutate data in-place if either no one else has access to it, or it's wrapped in a mutex and you've taken a lock.
Immutability gives simplifies everything including incremental, parallel GC and hence reduces the pauses for GC.
This is the big idea behind Clojure and Datomic - immutability makes everything simple (less complex runtimes) and even more scalable (share nothing means no locks, mutexes, busy waiting) and in the case of Erlang - fault tolerant (strong process isolation - no threads with shared stack or any writable data).
What is funny - everything has been well-researched and right principles formulated by Erlang team decades ago, and the resulting product just works for telecoms.
The two fundamental principles they described are 1) Posix threads and fault tolerance does not match. 2) Imperative code and GC does not match. While Golang, Lua or PyPy are trying really hard to disprove the second principle, Java is an existential proof.
What makes LuaJIT so efficient regarding the GC is that you can allocate memory that is not managed by it (using the FFI). You have to if you need to use a lot of memory anyway, since depending on the architecture GCd memory can be limited to as little as 1 GB.
Functional programs, if anything, tend to create more objects than object-oriented ones. However:
(0) Objects tend to be shorter lived, because new versions of data structures will be written to new objects, rather than overwritten to old objects.
(1) Because functional programming deemphasizes object identities, the GC can duplicate or deduplicate objects with equal contents. It can even merge several nodes of a linked data structure into a single fat node, improving locality of reference. If object identities matter, these “optimizations” actually break your program.
(2) The invariant that old immutable objects can't point to newer ones (at least in a strict functional language, not a lazy one like Haskell) can be used to optimize the GC algorithm as well.
Immutability means the number of individual objects explodes.
It also means those are easier to garbage collect, so you can have several different algorithms acting on different time-frames, alleviating the problem for the slower ones.
Taking something like Eiffel or Modula-3 as examples, many others are possible, architectures just like in C++ are perfectly approachable.
Not doing so is just a consequence of developers that are trigger-happy with new and don't think about designing for performance.
The question is still interesting for the other two languages though.
i.e. something doing decent math rather than just reading and writing chunks of data.
since erlang is up to 100 times slower than java for calculation tasks.
thats 100 times more expensive in terms of hardware.
so i doubt its used much for distributed systems.
But i can see the confusion.
Others I've simply never even considered would be chosen.
For Haskell I remember reading about Cloud Haskell ( https://haskell-distributed.github.io/ ) which allows you to write Erlang-style systems and Sparkle ( https://github.com/tweag/sparkle ) by Tweag ( http://www.tweag.io/ ) which allows you to use Haskell with Apache Spark. But those are tools and libraries and not complete production systems.
I'd love to read about Haskell or Common Lisp distributed system implementations if you have any pointers!
The OP is talking about the system part
i.e. Not merely using a distributed system such as erlang, couch/mongodb, and pretending you can do distributed systems.