6 ms·
You can profile your Go programs and see how much time they spend in the garbage collector. If it's over 20% you probably have a bug. And of course Rust and C a
by t8sr 3y ago
You can profile your Go programs and see how much time they spend in the garbage collector. If it's over 20% you probably have a bug. And of course Rust and C also spend time on memory management, it just has the effect of making everything a little slower rather than having one big ticket item in the profiler.
Go is slower than C and Rust, IME, for two big reasons:
1) It puts a lot of stuff on the heap, that could've been on the stack. This wrecks locality and makes Go call malloc/free a lot. (Actually not the malloc, just a malloc.)
2) Rust and C++ offer fine control over things like dispatch, checked and unchecked casts, control for allocation, strings, etc. Go usually offers one unified thing that's simple to use. So a lot more calls are virtual, a lot more things are checked at runtime, etc.
I don't understand why garbage collection is so mythologized. TBH I don't even understand why people think `Arc` and `shared_pointer` aren't garbage collection. Is this just the effect of Swift and Rust's marketing ("look, ma, no GC")?
- coldtea 3y ago>I don't understand why garbage collection is so mythologized. TBH I don't even understand why people think `Arc` and `shared_pointer` aren't garbage collection. Because when people talk about garbage collection in the context of the dichotomy between Rust and Java or Go, they talk about the latter being automatic memory management based on various heuristics, with the related runtime overhead, non-deterministic, etc. Whereas Rust Arc is just reference counting and explicit, not to mention enforced at compile time. I don't understand how and why anyone would consider the two as the same thing. They are both GC in the same sense that cars and boats are both vehicles.
- lomase 3y agoDo you really don't see how a GC and reference counting are similar?
- coldtea 3y agoYes: in the same way a boat and a car are similar since they both "take you from A to B". Now, do you really don't see how a Java/GO style GC and reference counting are different? It's not like I didn't enumerate some characteristics that make all the difference - and that explain why people do not think of those two cases as just a single thing called "garbage collection", and also why people believe that ARC would have less overhead than a Java/GO style garbage collector.
- pjmlp 3y agoThere isn't a Java style GC, there are multiple implementations, including with reference counting GC algorithms. The language and runtime specification don't say anything about GC algorithms required by compliant implementations.
- t8sr 3y agoSorry, but it really sounds to me like you're repeating something from documentation or CS 101. Have you ever actually worked on a garbage collector? * ARC is not deterministic outside of toy examples * The runtime overhead of modern GC algorithms has a lower bound than ARC, and this has been proven formally * Reference counting is usually atomic, so it implies other wonderfully expensive things like CPU cache flushes, memory fences, etc. * The runtime overhead of deferred GC also happens outside of the hot path, and doesn't do unpredictable non-local things if you release a reference at an inconvenient moment * ARC is in fact a GC strategy. E.g. obj-c has autorelease pools that postpone deletion until later, so the hot path doesn't get interrupted if reference count of something reaches zero early. These things are a spectrum. Rust leans a lot on the borrow checker, and both Rust and C++ nudge you toward using fewer heap small allocations because it is inconvenient to use them. This is where a lot of the performance wins come from, not because reference counting somehow shaves off 20% of the program's runtime. In fact, ARC is not only a GC strategy, it is close to being the worst possible GC strategy.
- coldtea 3y agoSorry, but it really sounds to me like you're repeating something from theorical research on GCs, best cases, and cherry-picking counter-examples. * ARCs non-determinism doesn't matter outside of contrived cases. In practical use it's as deterministic as expected. * The runtime overhead of modern GC algorithms might have been proven formally to have a "lower bound than ARC", but like many things proven formally, it's seldom the case in practice. And this isn't also counting the memory overhead from using GC. Just like the oft-repeated "JIT can be faster than compiled because it has knowledge of the runtime" but in practice that only happens on special cases, and compiled C/C++/Rust still beats its ass. As for the runtime overhead of deferred GC happing "outside of the hot path", and yet every domain with a GC and high load/low latency has historically suffered GC pauses, and GCs have to be reworked to ever more complex byzantine schemes to try to reduce the issue. >*ARC is in fact a GC strategy. E.g. obj-c has autorelease pools that postpone deletion until later, so the hot path doesn't get interrupted if reference count of something reaches zero early. These things are a spectrum. Yes, like cars and 18-wheelers are a spectrum. And C++/Rust is on the other side of that spectrum compared to Java and Go.
- kibwen 3y agoThis comes down to the usual boring semantic argument over what the term "garbage collection" means. Instead, I'll use the term "automatic dynamic lifetime determination". In a Java-style GC, there exists code that runs at runtime to determine whether or not the lifetime of a piece of memory can be determined to have expired. In a reference-counted system, there exists code that runs at runtime to determine whether or not the lifetime of a piece of memory can be determined to have expired. Of course, in the latter case the "code" is just checking a single counter rather than doing fancy concurrent root-tracing, but the similarity is established, and the alternatives are clearly delineable: 1) "Automatic static lifetime determination": this is what Rust does with ownership, by statically analyzing your code and automatically inserting code to free memory at statically-determined points. 2) "Manual static lifetime determination": this is the classic C-style memory management, where the author manually inserts code to free memory at statically-determined points.
- eru 3y ago> Of course, in the latter case the "code" is just checking a single counter rather than doing fancy concurrent root-tracing, but the similarity is established, and the alternatives are clearly delineable: [...] You also need to do some fancy tracing for reference counting, because when a big data structure 'dies', you need to trace it completely. Of course, reference counting also pays the price of fiddling with its counters on every single read. So every read-only workload turns into a read-write workload. Garbage collection has no such overhead until you start the collector. (Basically all the overhead is amortised.)
- eru 3y agoReference counting can also take arbitrary amounts of time.
- KingOfCoders 3y agoHeard the heap argument a lot, but until your "This wrecks locality and makes Go call malloc" it didn't make sense to me, thank you!