Hacker News new | ask | show | jobs
by haberman 3535 days ago
> Why can't you acknowledge that there are problems that have GC as the only and best solution?

GC is traditionally bundled into a language runtime in a way that imposes global costs, and cannot be opted out of. The GC interrupts your program in a way you have no direct control over and scans the global heap of all objects you have ever created.

C++'s philosophy is zero-cost, opt-in abstractions. So naturally anything that you can't opt out of is going to rub C++ programmers the wrong way.

Implementing GC within the language, in a way that is entirely opt-in, is fine. Any muttering under the breath when discussing this is, IMO, just acknowledging that most of our experiences are of "bad" GC, to the point that we almost don't want to use the same word when referring to "good" GC.

1 comments

> C++'s philosophy is zero-cost, opt-in abstractions.

It's a nice philosophy, but unfortunately C++ itself often fails to deliver unless the programmer is an absolute expert on the underlying semantics. E.g. forget to pass a vector by reference or as a pointer, and you get a complete O(n) copy underneath. With other data structures, one can implement efficient copy constructors to make this pretty cheap. When an abstraction leaks details of the underlying implementation that then lead to huge non-obvious costs, that is not an abstraction.

Another example is that C++ heap allocation relies on an underlying allocator, which has overhead in managed chunks of memory, size classes, etc. The underlying allocator's alloc() and free() operations are not constant time. In fact, they are almost always outperformed by the allocation performance of GC'd languages, where bump-pointer allocation and generational collection make these overheads very, very cheap.

C++ is an expert's language, no doubt about it. But wielded properly, it does deliver on its promise pretty well, especially with recent language evolution.

C++ lets you use a bump allocator if you want. malloc()/free() are just functions that you don't pay for unless you call them. That is the point! And as others have pointed out, many common data structures let you swap out the calls to malloc()/free() for something else if you want to.

Every data structure in C++ has an allocator override. If you want to use a bump pointer allocator, you can use a bump pointer allocator. In fact, this is a very common optimization in game software during time-critical frame rendering. Allocate a large chunk of memory to act as a flywheel, then use a bump allocator against this chunk of memory while performing data-heavy crunching, then reset the bump pointer at the end of the render cycle. All memory is freed at once, without the use of a more intrusive garbage collector.

As they say, C++ gives you enough rope to hang yourself. It's pretty unapologetic about not being a language meant for everyone. But, sometimes, one needs to drop down to a lower level language to boost performance. I like to apply the Pareto Principle: 80% in a higher level language, and 20% in a language like C or C++.

Sure, we use our own arena allocators with varying degrees of success in my current project (V8--JavaScript VM implemented in >800KLOC C++). The fact that C++ allows you to peer behind the curtain is more its own admission that it is inadequate to meet all use cases. It always allows one to resort to "manual" control. Usually that manual control comes at the cost of literally violating the language semantics and wandering into the territory of undefined behavior. As tempting as this manual control is, my 15 years of C++ versus my 10 years of Java and JVM implementation makes me feel like C++ causes far more trouble than it's worth, especially for projects that have no business at all trying to outdo the defaults.
I think that's mostly your opinion. One can remain very tight to the language semantics and still get a lot of fine-grained control over performance in C++.

No language can meet all use cases. Every language has features that it is best suited for, and features that are a bit of a pain. For languages like C and C++, the pain points are higher-level programming semantics. For languages like Java / JVM, the pain points are fine-grained control and low-level bit twiddling.

I recommend an 80/20 split. Write 80% of your code in a high-level language of choice that mostly meets your goals. Profile the hell out of it, then use that language's FFI to refactor the slow bits in a lower level language like C/C++.

There will be constraints, such as system level programming or embedded system programming, where a language like C/C++/Rust can't be avoided. But for general purpose work, the Pareto Principle works pretty well for balancing time-to-market with performance.

> Usually that manual control comes at the cost of literally violating the language semantics and wandering into the territory of undefined behavior.

What makes you say this?

Along the same lines, the thing that is annoying me now is return value optimization. It's apparently required by the standard in specific circumstances, but there's no (obvious) syntactic indication of whether you are using it.

So I'm sticking with out params for now.

You can implement allocators which are very fast, much faster than generic malloc and free. Then people that do care about this level of performance can write their own ones, optimised specifically for the platform they are using and problems they encounter.
It'd be nice if people actually replied with arguments instead of downvoting.