Multiple instances and memory space ( VST 2.4 )...

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

mystran wrote:
cpu.running.hot wrote:share the same stack and heap ( in most or all hosts )
Any design involving sharing stacks is doomed to fail. :)
I think I miss-communicated... Users have posted that they use global or static variables between instances, so by doing so, both instances are using the same stack. Otherwise the static or global variables would not share the same values. An global or static variable used as an instance counter, for example, would always return 1 if the stacks were different. So when I said "sharing stacks", I meant they are loaded into the same process and have the same stack by default.
Miles1981 wrote:Once the dll is loaded in memory, the static variables will be shared by all instances
Again, this is well understood. So unless there are "specific" DAW's out there that do not follow this rule, due to something specific in their implementation, then all is good :D

cpu

Post

cpu.running.hot wrote:
mystran wrote:
cpu.running.hot wrote:share the same stack and heap ( in most or all hosts )
Any design involving sharing stacks is doomed to fail. :)
I think I miss-communicated... Users have posted that they use global or static variables between instances, so by doing so, both instances are using the same stack.
NO!

The stack is a piece of memory used for storing local variables, function call arguments and return addresses, and other such book keeping. Stack is literally a linear piece of memory where you add and remove stuff at one end and keep track at where the current end point is (that's the stack pointer). Before a function returns, it must "pop" anything from a stack that it pushed during it's execution, or the program will crash so anything in stack only exists temporarily.

Each thread has a separate stack, because otherwise they wouldn't be able to execute concurrently. While it is usually (at least in main-stream operating systems) possible to access data from another thread's stack, it's almost always a terrible idea (which is what I was referring to in the above comment).

Heap on the other hand is a term used to refer to the memory region (or possibly several regions), managed by some memory allocator (possibly several again; usually malloc()/new talk to a lower-level allocator that works on page-granularity) where various things can be explicitly allocated such that they remain in existence independent of the actual program flow, until you're done with them. The CRT will give you a single global/shared heap, but you can create custom/private ones (either inside or outside the standard heap) if you really feel like it.

When you load a DLL (or the main application), the module loader pick a part of process address space (outside the heap in the malloc() sense) and puts all the code and data (including global variables) for the loaded module there.

So what you really want is just a shared address space! [and possibly knowledge of the fact that any given DLL is only loaded at-most-once and then additional requests to load the same module just bump a reference count]
Otherwise the static or global variables would not share the same values.
Global variables are called global precisely because they do NOT live in the stack (like local variables do) and hence persist between function calls. In fact they do not live in the regular heap either, they live in the data segment (which was created as part of loading the module).

Post

The static keyword has different meanings with how it's used. As used like a local variable, it persists over function calls, but does not live on the stack! It resides in a data segment of the process image which is created at compile time.

Just realized I wrote exactly what mystran did before I read it :dog:

Post

You are correct, my bad. Previously I was mentioning the heap/stack and I did mean the shared address space. I rarely need to consider them, so I got it mixed up. But yes, shared memory space. There was some confusion above regarding "shared memory" vs "shared memory space", so I was trying to be explicit and emphasize the "shared memory space" regarding stack/heap, and not something like this...

http://msdn.microsoft.com/en-ca/library ... 85%29.aspx

Yes, so I meant heap.

Thanks for the correction :D
cpu

Post

camsr wrote:The static keyword has different meanings with how it's used. As used like a local variable, it persists over function calls, but does not live on the stack! It resides in a data segment of the process image which is created at compile time.

Just realized I wrote exactly what mystran did before I read it :dog:
I was careful to say "global" rather than "static" to try to avoid this issue, but .. in an effort to entertain you guys with some more pointless pedantry, "static" has at least 3 different meanings in C/C++ family of languages:

- for regular globals, it makes the symbol (name of the variable) local to the translation unit (whatever produces a single object-file).

- for class members it turns them into regular global variables, except for the scoping rules

- inside functions, it turns the variable into a locally-scoped global variable that is initialized on first call to the function (hopefully in a thread-safe way, though check your compiler documentation if you really insist on using these; personally I do my best not to touch these with a ten-foot pole)

I also simplified a bit above, because in addition to the "proper" data segment (or section of the file; on modern systems such "segments" are not really "segments" in the hardware sense), globals can also go into .rodata segment (so constant data can be mapped read-only) or .bss segment (anything initialized to zero and/or not explicitly given a value typically goes here and doesn't get stored into the DLL file explicitly). Depending on the particular linker and/or loader, they might then end up in various places in the process address space (eg. .rodata in particular sometimes gets combined with code, sometimes with data, and sometimes might end up as a separate block; you might or might not get a page-fault if you try to modify such a variable). :)

Post

mystran wrote: - inside functions, it turns the variable into a locally-scoped global variable that is initialized on first call to the function (hopefully in a thread-safe way, though check your compiler documentation if you really insist on using these; personally I do my best not to touch these with a ten-foot pole)
The only way I use static locals is for constants that can not be inlined directly. For example __m128 constants.

It should be safe in all cases to initialize these constants multiple times from multiple threads when you're merely filling the value, such as:

static float pi = acosf(0.0f)*2.0f; // this is a BAD example because it CAN be inlined

The overlapping stores shouldn't pose any issue.

Anything more complex than this however I agree is untenfootpoleable.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

aciddose wrote:
mystran wrote: - inside functions, it turns the variable into a locally-scoped global variable that is initialized on first call to the function (hopefully in a thread-safe way, though check your compiler documentation if you really insist on using these; personally I do my best not to touch these with a ten-foot pole)
The only way I use static locals is for constants that can not be inlined directly. For example _m128 constants.
Actually, this can generate the worst possible code [well, it's "safe" but that's about it]: a check for whether the constant has been initialized (which is retarded and could be considered an optimizer bug, since just treating it as a regular constant would be observationally equivalent .. but it happens).

So in practice you might observe certain compilers producing much better code if you either place the constant outside the function, or just remove the static qualifier (in which case the value is stored in .rodata and the actual variable can be optimized away).

Don't know if that's still a problem in the current versions, but at least for older MSVC static locals (constant or not) are a big no-no.

Post

Yep the initialization code is definitely crap, you wouldn't put this inside anything time-critical. For easily arranging some constants I don't want to "officially" store as part of an object (even as static / global) when doing something like computing coefficients however it makes sense.

Anyone considering using them should definitely keep the ten foot pole rule in mind:

1) don't touch it with a ten foot pole
2) if you do, definitely use a pole at least ten feet long

Mostly I've been quite happy to use them while doing R&D. They get removed during optimization.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

So unless there are "specific" DAW's out there that do not follow this rule, due to something specific in their implementation, then all is good :D
It seems that Bitwig sandbox every plugin by default:
http://www.bitwig.com/en/support/faq.html
Bitwig Studio runs VSTs in a separate process. This prevents Bitwig Studio from crashing when a plug-in misbehaves.
So I would advise against this trick.

Post

ponce wrote:
So unless there are "specific" DAW's out there that do not follow this rule, due to something specific in their implementation, then all is good :D
It seems that Bitwig sandbox every plugin by default:
http://www.bitwig.com/en/support/faq.html
Bitwig Studio runs VSTs in a separate process. This prevents Bitwig Studio from crashing when a plug-in misbehaves.
I just downloaded the demo for Bitwig Studio and tested it out. It does indeed create a second process called "BitwigPluginHost32.exe" on windows. However, all instances of the plugin go to that same process, so they do indeed share the same memory space.

Thanks for bringing this to my attention. At this point, I'm pretty certain that most if not all major VST hosts will keep multiple instances of the same plugin in the same process as each other ( not necessarily the same process as the host as in this case), but still keeping my eye out for exceptions.

So far, I have personally tested FL studio and Bitwig Studio now and they both exhibit the "multiple instances of the same plugin share the same memory space" rule.

cpu

p.s. I realize this may seem like a trivial thing, but I'm new to VST development ( not development in general ), so I'm erring on the side of caution and trying to predict problems ahead of time.

Post

Just try not to make any assumptions at all.

Don't use a global var to count instances, it's impossible to do in any reliable way. You'll need to use a system-specific functionality to handle something like that.

For example:
http://msdn.microsoft.com/en-ca/library ... s.85).aspx
http://en.wikipedia.org/wiki/Semaphore_ ... ramming%29

Exactly what you use will depend upon your needs. If you absolutely must count instances though you must use a stable interface to communicate with the OS to facilitate this.

As far as singletons or globals of any nature:
http://stackoverflow.com/questions/1379 ... singletons

I agree that the only reason to use a singleton is for resource management. In many cases you'll want to manage bitmaps or other data that you need allocated and initialized only once between many instances.

In this case it wouldn't hurt to use a singleton rather than more advanced inter-process communication with a "database" process because if you end up with instances spread across multiple processes they'll just end up making multiple allocations of that data, once for each process. It acts as an automatic optimization. If it can be optimized, it is. If it can't, the fallback is just to load up another copy of the data local to the current process.

For global "user banks" and configuration, use the ultimate portable database: stdio. Use the file system.

On windows, osx and linux there are locations for you to store application data such as configuration, presets, skins and so on. Use them.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

I'm doing more than counting instances. Without divulging details, the plugin is specifically designed to work with several instances together. It's core functionality depends on having the instances communicating with each other, at real time.

So essentially, each instance is a node of a larger, single program.

Without getting into a philosophical discussion regarding "just try not to make any assumptions at all", we make assumptions all the time. The trick is not just to assume, but then test the assumption in as many use-case scenarios as reasonably possible. There will always be unsupported cases as well, and we can account for them.

So, if we discover that all "major" VST hosts behave such that multiple instances of the same plugin to share the same memory space, then we can safely proceed with the knowledge ( not an assumption anymore ) and design the plugin around that. For hosts that do not behave this way ( if any ), then we can simply not support them. To develop a complicated socket, memory mapping, inter-process communication scheme just for a possible (unknown) case will be a waste of valuable development time. I have worked with all of these methods in the past and understand how/when/where to use them. Mind you if I do find a major DAW package that requires the use of these methods, I will do so.

Experience has taught me that if you try to account for every single possible scenario, you can spend more development time than it's worth. Time is money after all, and simple is better than complicated. As long as you handle the unsupported cases gracefully ( i.e. friendly error message instead of having the plugin crash ), than all is good.

cpu

Post

If you structure your code correctly there will be little to no difference between the naive implementation of your communication via a singleton + semaphore local to the process vs. a larger implementation of the same class with a system-specific inter-process compatible communication mechanism.

If you've worked with these before you should know already that there is very little difference whether you're talking between processes or not. If the communication links are not unidirectional and established via the slower semaphore method, you need to use the semaphore for all communication.

So taking that into account it seems to me no matter what you're doing it will already need a complex object oriented implementation to work at all. Planning ahead to allow yourself to extend it to support inter-process communication would be simply "a good idea".

You can then forget about worrying about whether all hosts will operate all instances in the same process or not. If you encounter a case where they do not, move to implement the more extensive inter-process communication.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

Indeed, there is no agument there. Singleton/sephamore, etc. are all good and necessary for proper functionality in a multithreaded environment. We are on the same page in this area.

The only reason for possibly considering the other options are if the vst's were in different processes.

As mentioned before, I'm pretty confident now that my fears of separate processes for the same plugin were not justified, however, i felt it important to investigate this first, so i would not have to restructure later.

Post

Would be interesting to know if Reaper also groups plugins in the same process likewise.
I've seen it such plugins working together for sidechain compression and always wondered how it was done.

Post Reply

Return to “DSP and Plugin Development”