I’ve got a question about how allocation works for plugins. Correct me if I’m wrong but since plugins are instantiated with “new”, they are all on the heap to start, but, after testing, it seems that there seems to be to some cache locality benefit to using C-style arrays or other stack-based members (of AudioProcessor) even though it is on the heap already.
Question:
Does the executable code section (member functions) live in the same address space of the member data?
Is your question related to where the plugin binary is loaded in memory or how instances of that plugin are managed? Anyway the functions called in your plugin are proceeded exactly as the functions of the host. By the way note that i’m not an expert in VST and such.
A member is allocated inside its owner. It may happen to contain a pointer to some memory out of itself, and then out of its owner, allocated with new. A member is not on the stack or the heap by itself: it is wherever its owner happens to be.
If you create an object with a C-style array or std::array member in a local variable, it’s on the stack, with the array inside of it.
If you create the object with new, it’s on the heap, with the array also inside of it.
If you create an object with an std::vector member in a local variable, it’s on the stack, with the std::vector inside of it, but the std::vector contains a pointer to the actual data on the heap.
If you create the object with new, it’s on the heap, with the std::vector inside of it, but the std::vector contains a pointer to the actual data in some other place on the heap.
Even if the whole thing is created on the heap, a member that’s fully contained by its owner may benefit locality. It also may not: if it’s a large block, and the stuff before and after happen to be used together in a time critical section, it can make things worse. As a rule of thumb, I tend to put buffers and other large blocks on the heap, but small arrays / structures as fully contained members, especially if they’re used in conjunction with other members in time critical sections.
This is really the key here.
A class is molded together by the class itself plus space for each member variable.
There are a few things that are never on the stack:
stuff that can or cannot be present (nullable)
exception std::reference_wrapper: this will reserve the necessary space that the object can be constructed there later
stuff that can dynamically change the size:
vector
AudioBuffer
juce::Image has the data reference counted on the heap
and some more
Note that std::array can be on the stack, because the size is known at compile time (not to be confused with juce::Array)
Also note there is one stack for each thread. Exposing thread locals cannot be done safely without priority inversion, because you have to lock the exposing thread while the other is using the data.
IIRC also std::optional has the same behavior, i.e. it can contain nothing but nonetheless it reserves the space to contain its value when set to something.
Thanks for the addition. I had not check so far but from cppreference.com it seems you are correct:
If an optional<T>contains a value , the value is guaranteed to be allocated as part of the optional object footprint, i.e. no dynamic memory allocation ever takes place. Thus, an optional object models an object, not a pointer, even though operator*() and operator->() are defined.