https://docs.microsoft.com/en-us/aspnet/core/performance/per... https://docs.microsoft.com/en-us/dotnet/standard/garbage-col...
Even if we ignore the latter, gc will can add 2x for objects that you don't do much with.
And then there's the cache effects. Close to top-of-stack is pretty much guaranteed to be in L1 and might even be in registers. Heap allocated stuff is wherever. Yes, usually L2/L3 but that's at >2x the latency for the first access.
Cache misses are indeed true, but I think we should not pose the problem as if the two alternatives are GCs with pointer chasing and some ultra-efficient SoA or array-based language. Generally, most programs will require allocations and those will cause indirections, or they will simply not loop over some region of memory at all. Then GC really is cheap and may be the better tradeoff (faster allocation, parallel reclaim). But yeah, runtimes absolutely need a way for value classes.
I thought that the comparison was between "heap-allocate every object" vs "zero allocation." (C/C++ and similar languages which make it easy to stack-allocate objects, which is not far from zero-allocation.)
If the application is such that zero-allocation isn't easy, then that comparison doesn't make sense.
However, we're discussing situations when zero (or stack) allocation is possible.