So, from a lifetime perspective, it's still pretty stack-like. If you make a string, and you don't refer to it from outside the function, it's gonna get noticed by escape analysis and be like anything else on the stack.
>the string itself is on the stack, but the data is on the heap
So both are regular heap allocated values? So the majority of data is still stack (edit: heap) allocated (safe ints, floats and some simple structs, I suppose), so I fail to see how ``young objects live and die young on the stack''.
It is true that some GCed languages does not have stack allocation at all, but many of them (java, haskell) have very powerful inlining capabilities to compensate it. I still don't understand why I don't need generations for allocating to-die-soon strings in a more efficient ways.
You are correct. Properly-implemented copying generational GCs are the only things that work to optimize allocation of strings, arrays, and objects judged to be escaping.
> So the majority of data is still stack (edit: heap)
It doesn't matter that the string's data lives somewhere else. It matters where what owns it lives. The string itself, that is, the pointer and length, both live on the stack, and so, it ends up working like a stack allocated value.
> I still don't understand why I don't need generations for allocating to-die-soon strings in a more efficient ways.
Because the string itself isn't on the heap, and therefore, a generation wouldn't help it.
How does that follow? If the string is a pointer to a heap array plus a length, then "it ends up working like a stack allocated value" would only be true if the underlying byte array was freed immediately when a scope ended. But that isn't the case because the heap is a GCd heap, Go doesn't have explicit frees.
This is one sort of reason why stack allocation isn't a cure-all - stack allocating a wrapper doesn't help you much if the bulk of the data is still in the heap. What can help, in this situation, is if the compiler can prove (or speculate that) the array is small and then scalar replace the array. However I think doing that sort of thing requires a profiling JIT compiler because statically proving array sizes ahead of time is very difficult.
I've never seen a GCd heap where you can explicitly free things outside of the Boehm conservative GC. I haven't seen any reference to Go supporting such a thing either.
I think all that happens when a string goes out of scope is that the byte array becomes eligible for GC.
That sounds trivial to fix... go cheats on all sorts of things with the built-in types (maps/slices/strings) that you can't do as a user of the language, seems they could easily implement something that explicitly frees the heap space when the string pops off... the free call could be inlined right into the compiled code by the compiler.
> This kind of thing happens in Ruby a lot; the VM will allocate stuff through malloc that never actually hits the GC; see Aaron Patterson's work.
IIUC there's a really huge difference here. Go can quickly allocate the byte array into the GC managed heap. MRI Ruby has to make a slow call to malloc(). Worse still, when collecting we can't just mark that memory available again. Ruby has to detect the type of each object and call free() accordingly.
My understanding is, the string itself is on the stack, but the data is on the heap https://github.com/golang/go/blob/2f2e8f9c81d899d8eefb1f2f98...
Likewise, the "double pointer" of an interface is on the stack, but points to the heap: https://research.swtch.com/interfaces
So, from a lifetime perspective, it's still pretty stack-like. If you make a string, and you don't refer to it from outside the function, it's gonna get noticed by escape analysis and be like anything else on the stack.