Edit: That is, is it typical for GCd programs to outperform manually-memory managed programs in multi-threaded environments?
The caveat is that properly engineered C/C++ hardly ever uses malloc() or similar to manage memory. Relative performance in real systems would be between a garbage collector and one of myriad not-malloc() mechanisms typical of C/C++ that are much faster and often safer than using malloc().
If you are among that subset of programmers that uses malloc() ubiquitously, an argument might be made that a garbage collector is a better choice if you have plenty of memory. However, this argument would be about safety; if you are using malloc() ubiquitously then you obviously do not care about performance prima facie.
"Properly engineered" probably isn't the right term. C/C++ code written for speed would not perform heap allocation in its main processing. However, this is true with garbage collected languages as well.
Removing allocation means removing an entire class of data management operations from your program regardless of the language's memory architecture. It's a general speed-optimizing programming style that is irrelevant to manual vs automatic GC.
Relative performance in "real systems" written for speed would be a manual memory management language without using malloc/free, vs GC language without using allocation. However, the more interesting case is comparing real systems which do perform runtime allocation; it's highly dependent on the particular program's characteristics, but GC systems can be both a lot faster and simpler architecturally.
Not just code written for speed. Stack allocation is easier. There's not much that can go wrong. Heap allocations on the other hand you have to be careful not to leak or free multiple times or free a wrong pointer. So after you've debugged one too many of these bugs, you learn to avoid heap allocations where possible just to make your own life easier.
(This is my experience with C, I don't have enough experience with C++ to know whether it sufficiently takes care of some of that complexity to the point where heap allocation becomes as easy as stack allocation.)
In theory - in practice trying to avoid GC in managed languages is like putting on a straightjacket, eg. JVM doesn't even have value types, and higher level languages - just forget about it :D
The closest I realistically got to this is C# and even then there are all sort of caveats because even if your code doesn't allocate it's a standard pattern to not care about allocation and stuff allocates all over the place and there are no tools to figure out what allocates and what doesn't from code so you just have to assume everything does unless you wrote it or read/profiled it.
Arrays, records, objects can be stored on the global segment, stack or heap. So they stress the GC as much as new/malloc stress the C/C++ memory manager.
Why in the world is it fair to compare C/C++ programs that don't allocate to Java/C# ones that do? Another way of saying this is if you are using new ubiquitously then you obviously do not care about performance prima facie.
At the end of the day GC'd versus manual memory debates seem to fall into 2 axis. Complexity vs correctness and total memory available vs concurrent access to said memory.
At this point in time the difference in performance between a C application and a Java one comes down to correctness and access to low level memory layout primitives, not GC vs manual memory.
The other reason is that languages that make you allocate explicitly with some burdensome syntax, that allocation is in your face, you cannot be unaware of it. Whereas in other languages someone may be blissfully unaware and still churn out pieces of usable software. The bite comes much later. On languages that were not designed around garbage collection, it is usually a whole lot harder to avoid allocation.
Is that possible? I thought all objects are heap allocated in Java?
Also escape analysis is a thing Java can do to avoid heap allocating objects in general: https://docs.oracle.com/javase/7/docs/technotes/guides/vm/pe...
...which meant you could run (a limited subset of) Java programs on very, very small devices. 8-bit microcontrollers. Such as Maxim's iButton devices, or smart cards.
My memory says that the standard is called kJava, but it seems to be ungoogleable these days, so I could be wrong (and Oracle ate all of Sun's documentation).
If most objects die before a GC cycle, it can be faster to just stop all threads and reclaim that memory in bulk as Boehm does.
More broadly, if batched deallocation is indeed faster, you can get that behavior in a manually memory-managed scenario. You aren't forced into prompt reclamation. It's just that prompt reclamation is usually faster for cache reasons and improves memory consumption, so malloc implementations take advantage of the opportunity.
The reality seems to be a bit different. In practice GC programs tend to use significantly more memory which can impact performance. And the trend towards low GC pause times costs additional CPU. Beyond that we're now using much larger multi gigabyte GC memory pools which can also lead to poor GC performance.
So overall people these days see lower performance with GC systems compared to malloc.
But the real question, in my mind, is whether a well-tuned systems-level program that uses stack, arena, and heap allocation with a good allocator like jemalloc ends up being better with a garbage collector. And it's really hard for me to see how that could possibly be the case. Performance-conscious systems programmers will use the stack as their nursery, gaining all the benefits of the nursery without the copying, tracing, or delayed reclamation. Modern mallocs like jemalloc or tcmalloc are incredibly good at minimizing fragmentation and satisfying requests quickly, using thread-local caches to avoid synchronization. Most of the time, the tenured generation needs the same bookkeeping that a modern malloc does, so you're not really gaining anything by using GC for that generation. And a GC always has some kind of mark or tracing phase (not to mention at least a write barrier if you want your pause times to be reasonable), which is pure overhead over manual memory management.
Sadly HP/Compaq killed the Olivetti/Digital unit and Modula-3 died, but its ideas can still be applied in modern languages, assuming similar capabilities.
"With only three times as much memory, the collector runs on average 17% slower than explicit memory management. However, with only twice as much memory, garbage collection degrades performance by nearly 70%."
In which language ?
There are GC enabled programming languages with global and stack static allocation, for example Oberon just to cite one, which was on top HN a few days ago.
Bohem GC allocates in large(ish) slabs, so it can manage many small objects more efficiently than OS malloc. Modern malloc replacements like jemalloc also do this for manual allocation.