Live data from Hacker News

High-performance garbage collection for C++

v8.dev

11–20 of 25 posts

Re: High-performance garbage collection for C++

#11
post #7

Interesting, but I still see `std::shared_ptr` and careful ownership as the C++ way. GC feels like adding another runtime dependency.

V8 has a C++ GC because if you have shared C++ JS object graphs you either need extra machinery to find cycles in individual code-paths (very error prone) or let C++ data live in JS objects anyhow (initial V8 interop way).

Writing extensions for a GC'd language is painful or you let your interop code live in a GC world, I'm pretty sure you more or less always end up with the latter unless you add a way for the hosted language to support some way to control the host language (like JS code "understanding" std::shared_ptr's but that probably adds other securiy risks in terms of low-level exposure).

It's an engineering tradeoff, if you're 100% C++ you don't need that.

Re: High-performance garbage collection for C++

#12
post #9

It's a non-moving collector. It might be high performance by the standards of C++ garbage collectors, but I doubt its performance could be anywhere near what a decent JVM can manage, especially in the absence of finalizers/destructors. As Ron Pressler ( pron here on HN) has been emphasising recently, [0] the 'sweep' phase of a moving garbage collector is unaffected by the size or number of dead objects in the heap (a…

I think it's a good thing to separate requirements/problems/etc. 1: Java has an horrendeous amount of unnecessary garbage due to their erasing generic design (until Valhalla ever arrives), there was some old blog post that chronicled the creation of Dictionary for C#'s System.Collections.Generic (as opposed to the initial Java-like System.Collections) and how it reduced garbage-load by an magnitude. Java chose compat…

> You don't need the absolute best GC's when your language doesn't create the same amount of GC-pressure.

Conversely, if you language (e.g. LISP) does create a huge amount of GC-pressure, it will lead to the development of the absolute best of GC technology.

Edit: no need to downvote this, it won't change the fact that both GC itself and generational GC in particular was invented specifically to make it possible to run LISP programs on the actual existing hardware in reasonable time.

Re: High-performance garbage collection for C++

#13
post #7

Interesting, but I still see `std::shared_ptr` and careful ownership as the C++ way. GC feels like adding another runtime dependency.

Yeah but that doesn’t work when you’re using C++ to implement another language where people can do anything they want, including circular references.

Re: High-performance garbage collection for C++

#15

It's a non-moving collector. It might be high performance by the standards of C++ garbage collectors, but I doubt its performance could be anywhere near what a decent JVM can manage, especially in the absence of finalizers/destructors. As Ron Pressler ( pron here on HN) has been emphasising recently, [0] the 'sweep' phase of a moving garbage collector is unaffected by the size or number of dead objects in the heap (a…

>> In what sense are they treated as one heap? How can they be, given that V8 uses a moving GC for its JavaScript heap? Does it just mean there's some mechanism for a C++ object to refer to a JavaScript object, and vice versa?

There's a dead comment below that I vouched that goes into some detail: https://news.ycombinator.com/item?id=49709739

Essentially yes, C++ objects can refer to JS objects and vice-versa, but they don't share a heap, since, as you noted, the V8 JS heap uses a moving collector. They pass the Tracer object back and forth between the JS heap and C++ heap during GC.

Re: High-performance garbage collection for C++

#16
post #9

It's a non-moving collector. It might be high performance by the standards of C++ garbage collectors, but I doubt its performance could be anywhere near what a decent JVM can manage, especially in the absence of finalizers/destructors. As Ron Pressler ( pron here on HN) has been emphasising recently, [0] the 'sweep' phase of a moving garbage collector is unaffected by the size or number of dead objects in the heap (a…

I think it's a good thing to separate requirements/problems/etc. 1: Java has an horrendeous amount of unnecessary garbage due to their erasing generic design (until Valhalla ever arrives), there was some old blog post that chronicled the creation of Dictionary for C#'s System.Collections.Generic (as opposed to the initial Java-like System.Collections) and how it reduced garbage-load by an magnitude. Java chose compat…

One thing that people almost 100% of the time miss in the Java vs C# generics discussion, is that .NET was actually being designed with generics support, but they weren't ready on time for the 2001 launch, and there were some internal fighting of they should even be delivered, thus the work was only finalised on time for .NET 2.0.

This is documented by one of the authors, Don Syme of F#'s fame, on his blog posts and the F# HOPL paper.

Meanwhile Java went through Pizza and other alternative proposals until landing were it did.

However from my point of view both ecosystems could have been much better on version 1.0, had they taken the learnings from Cedar, Modula-3, Eiffel, Sather and Oberon, among other predecessors, regarding AOT compilation and value types, in GC enabled languages.

Re: High-performance garbage collection for C++

#17
post #8

It's interesting reading this, wondering how much C++26 static reflection could improve the ergonomics of a system like this. Like, if you tag the class with a specific attribute, can you have have it generate the Trace() function automatically? Can you have it automatically wrap the other GC classes using the Member template? I think implementing the Trace() function might be doable (you'd do it in the GarbageCollec…

Automatic trace definetly yes, however since they also removed GC support from the standard it's quite useless (exact object tracing is useless if you cannot accurately scan the stack). Also co-routines with it's under the hood management of coroutine stack probably would've complicated GC support even more...

C++11 GC was useless from the start, I don't get how it ended up being voted in.

At the time the two main customers of having a C++ GC would be Unreal C++ and C++/CLI, the design that landed on the standard serves neither of them, thus no one adopted it.

Re: High-performance garbage collection for C++

#18
post #7

Interesting, but I still see `std::shared_ptr` and careful ownership as the C++ way. GC feels like adding another runtime dependency.

The C++ way is unique_ptr, which covers about 80% of the needs and in cases where it works it is probably better than a GC. Shared_ptr covers about 15% of the cases, but that still leaves 5% where you need a full better GC.

GC is another runtime dependency (or a really complex intrusive addition to your code), so if you don't absolutely need it I would avoid it in C++. A lot of C++ programs can avoid it with careful design.

If you need a better GC anyway, in the real world modern GC algorithms are likely more performant for your use case than shared_ptr. Thus shared_ptr should be thought of as the C++ answer only because a "real" GC is hard to implement, but if you implement a real GC anywhere just use it to replace shared_ptr as well. (I lean to use unique_ptr even when GC is available, but I reserve the right to change my mind if someone presents evidence)

Re: High-performance garbage collection for C++

#19
post #9

It's a non-moving collector. It might be high performance by the standards of C++ garbage collectors, but I doubt its performance could be anywhere near what a decent JVM can manage, especially in the absence of finalizers/destructors. As Ron Pressler ( pron here on HN) has been emphasising recently, [0] the 'sweep' phase of a moving garbage collector is unaffected by the size or number of dead objects in the heap (a…

I think it's a good thing to separate requirements/problems/etc. 1: Java has an horrendeous amount of unnecessary garbage due to their erasing generic design (until Valhalla ever arrives), there was some old blog post that chronicled the creation of Dictionary for C#'s System.Collections.Generic (as opposed to the initial Java-like System.Collections) and how it reduced garbage-load by an magnitude. Java chose compat…

> You don't need the absolute best GC's when your language doesn't create the same amount of GC-pressure.

Oilpan manages DOM nodes in Chromium. Some webapps running on POS terminals like to create and destroy thousands of them very rapidly (I’ve seen such code generated by some Java->JS transpiler used by a customer). On lower-end devices this leads to long GC pauses (10-90s) blocking all interaction. This GC is a joke for the purpose it is being used, where the heap pressure is controlled from JS code from a webapp. A generational GC would have no problems with that. Seeing Oilpan described as “high-performance” in Google docs always makes me laugh, having profiled it for some customers in the past, who were having problems with it.

Re: High-performance garbage collection for C++

#20

It's a non-moving collector. It might be high performance by the standards of C++ garbage collectors, but I doubt its performance could be anywhere near what a decent JVM can manage, especially in the absence of finalizers/destructors. As Ron Pressler ( pron here on HN) has been emphasising recently, [0] the 'sweep' phase of a moving garbage collector is unaffected by the size or number of dead objects in the heap (a…

I've spent the last few years on a library-only collector for C++ (SGCL, https://github.com/pebal/sgcl), also non-moving, so a few data points on the "can it compete" question. Disclosure up front: my project, and measured so far only on Apple Silicon/macOS, one machine, nothing tuned.

On the sweep: it's true that a non-moving collector's sweep is proportional to the number of dead objects, and a moving one's isn't. In practice that cost is small and parallel: the sweep walks per-page state bitmaps (about 1 ns per object), runs the destructors of the dead objects, and is spread over helper threads while the mutators keep running - nobody waits for it. What moving actually buys you is bump allocation and locality. Per-type pages with thread-local free bitmaps get allocation to about 5 ns per object on one thread and 9 ns on 24 threads, without a pause and without moving anything; ZGC does 3.6 ns and 26 ns on the same machine. On binary-trees at depth 21, ZGC is ahead on one thread (2.5 s vs 4.5 s) but with a 1.1 GB heap against 340 MB, and on four threads they tie (1.6 s each). Go, which is also non-moving, sits at 6.3 s / 219 MB there. So "non-moving" is not what decides it; the cost of the barrier, the marking and the sweep spread over cores decides it.

On destructors: they run on the collector's threads, in parallel, not on one thread, and the rule is the same as Oilpan's - a destructor must not touch other managed objects, because they may be dying in the same sweep. I don't have a Clang plugin to enforce it statically; there is a runtime check in debug builds and an explicit escape hatch (if_alive()) for the one legitimate case, a destructor asking whether a peer is still there. Oilpan's static verification is the nicer answer to that particular problem.

Where the approaches really differ is how the collector finds the pointers. Oilpan needs a Trace() method per class (or, as someone suggested above, reflection to generate them). SGCL builds a pointer map per type at runtime by elimination - a word that is ever found holding a value that isn't a managed address is data and leaves the map for good - so plain structs with tracked pointers in them just work, at the price of a couple of rules (no union of a pointer with data, stacks scanned conservatively). Different trade-off, not obviously worse.

Post reply on HN