What does GC cost?
For Caddy with an incredibly synthetic http only benchmark it costs about 2ms of latency and somewhat less throughput.
Worth it in an incredibly artificial benchmark? Perhaps. However when it comes to real world usage the cost is a significantly smaller piece of the pie.
Agreed, for most real world softwares, the cost of GC is irrelevant. But there are still some programs like databases or game engines where the cost can start adding up. That's when you measure and optimize.
Yet the three major game engines Unreal, Unity and Godot all have a GC on their infrastructure, and Capcom is quite happy with their .NET fork on RE Engine.
Also every single graphics application that uses Metal or DirectX, relies on reference counting as GC algorithm.
If the language is Java which puts the GC in nearly everything you're f...,
other languages have GCs and nonGC'd data so you go to the wonderfull world of manually managed objects with its use-after-free..
This assumes that you’re only ever running a single software at a time. Sure, 2-10x slower/memory consumption might not matter in a vacuum, but when every software is like this, you get machines that feel slower than they did 2 decades ago.
I used to write a lot of Javascript-like Extendscript scripts back when I was using InDesign a lot. The DOM's global object $ had a method to directly invoke the garbage collector. It made a difference certainly, but it was difficult to tell to what extent because InDesign itself gradually leaks memory and becomes more bloated the longer you use it in a single session.
> In Rust you pay for it by arranging your program in a way the compiler can verify.
I disagree with this. The sentence implies that this work is done in order to make the compiler happy, where my experience is that it forces the programmer to actually get it right.
I had an "aha moment" when I was frustrated at failing to express my intent to the compiler, and suddenly realised that the reason I couldn't "just say the magic words" was that my object ownership design was inherently flawed. I had to make large changes not to make the compiler happy, but to actually have a coherent design.
So no, it's not about what "the compiler can verify". That's like saying "my lawyer won't let me do this". No, your lawyer is your employee, not your boss. They're just saying that if you do this, then you may go to prison. It's not the same thing.
("unsafe" is the Rust way to go "thank you, legal department, but I'm making a business decision to take this risk. Your concern has been noted")
There is no need for garbage collection if you don’t form circular references. Just have a canonical direction and always keep weak references the other way.
Props for using a correct nomenclature. Reference counting is a "kind of automatic garbage collection. Tracing garbage collection is also a kind of automatic garbage collection.
> Think of a service that keeps a large cache in memory, or an index built out of millions of small objects that all point at each other. Every one of those pointers has to be followed on every cycle, for as long as the process is up.
That's a strange thing to assert, having acknowledged the existence of generational GC.
A generational GC might help with some part of the problem but does not solve it entirely.
Major collection still happen which will scan everything, just with a reduced frequency.
Long libed caches might still have pointers being mutated frequently (like an LRU cache), which will put objects in younger generation.
Golang does not have a generational GC. There are examples where long lived caches have been a problem in golang. One example - https://discord.com/blog/why-discord-is-switching-from-go-to...
Even things built directly on underlying malloc and free typically have some form of "garbage collection" in the malloc implementation for efficiency and performance (geometric sizing, thread caching, etc).
It's best to think about lifetimes and lifecycles where possible. Immutability where sensible and things like pool allocation are examples of this.
GC languages can result in quite pessimistic code because they encourage people to NOT think about what is going on. But people have also built functional HFT engines on things like the JVM by thinking about lifetimes and lifecycles.
There's a third way here. It could be called many things: Single ownership by default, automatic stack lifetimes, hidden unique pointers, etc. The main idea is that the lifetime of dynamic heap data is treated no differently to primitive stack data: Clean it when it goes out of scope. Rust and C++ require you to specify this manually, but Nim is unique among native-compiled languages in that is does it by default, with tools to opt-out. An advantage of this approach is that combining immutable values and static analysis can reduce most parameter passing to borrows, again, without needing the programmer to specify, by default. The main cost being that some assignments, especially crossing the variable-to-immutable line or vice-versa, would require a copy.
Not really the point of the post, but there is a third memory allocation paradigm where you can do manual allocations and deallocations, but without the mental overhead of having to remember to free memory because the compiler will force you to properly handle it. Mojo has linear type support so you can do something like this: https://x.com/melodyogonna/status/2085089269484343725?s=20
> The leaks that come from forgetting to free something go away entirely.
Not quite: In manually-managed and referenced counted languages with destructors, releasing resources, (open file handle, open socket, open connection to a database, ect,) happens when objects are cleaned up.
In a (tracing) garbage collected language, releasing resources is a very manual process. You might not have a memory leak, but leaking file handles or similar resources is a real problem with real consequence.
20 comments
[ 0.23 ms ] story [ 19.0 ms ] threadAlso every single graphics application that uses Metal or DirectX, relies on reference counting as GC algorithm.
How do you "optimize" the GC away after you wrote your entire database server in a language that uses it?
I disagree with this. The sentence implies that this work is done in order to make the compiler happy, where my experience is that it forces the programmer to actually get it right.
I had an "aha moment" when I was frustrated at failing to express my intent to the compiler, and suddenly realised that the reason I couldn't "just say the magic words" was that my object ownership design was inherently flawed. I had to make large changes not to make the compiler happy, but to actually have a coherent design.
So no, it's not about what "the compiler can verify". That's like saying "my lawyer won't let me do this". No, your lawyer is your employee, not your boss. They're just saying that if you do this, then you may go to prison. It's not the same thing.
("unsafe" is the Rust way to go "thank you, legal department, but I'm making a business decision to take this risk. Your concern has been noted")
That's a strange thing to assert, having acknowledged the existence of generational GC.
It's best to think about lifetimes and lifecycles where possible. Immutability where sensible and things like pool allocation are examples of this.
GC languages can result in quite pessimistic code because they encourage people to NOT think about what is going on. But people have also built functional HFT engines on things like the JVM by thinking about lifetimes and lifecycles.
> The leaks that come from forgetting to free something go away entirely.
Not quite: In manually-managed and referenced counted languages with destructors, releasing resources, (open file handle, open socket, open connection to a database, ect,) happens when objects are cleaned up.
In a (tracing) garbage collected language, releasing resources is a very manual process. You might not have a memory leak, but leaking file handles or similar resources is a real problem with real consequence.