FR: lock-free allocator

This a framework which is specifically recommended for audio-applications, these capabilities would be unbeatable sales arguments.

  • A multi-purpose low latency lock-free memory allocator and release pool suitable for audio-threads

  • lock-free messaging (sendChangeMessage() etc…)

PS: This is a feature request, not a question how to achieve this technics. And yes - i know that a lock-free allocator is not a solution for any kind of low-latency issues.
It’s just missing tool in the juce-toolbox.

8 Likes

+1
This is a constant source of trouble and should be adressed.

A huge +1 for built-in lock-free messaging. In pretty much any audio app, you will have to communicate between the audio thread and the GUI thread in a way, so this is a must-have.

It’s already there with AbstractFifo. Granted the API is not that great and would work better as a template container, but that’s not too hard to roll yourself.

What about signaling across threads? I know dave96 talks about using AsyncUpdater to combine a bunch of property updates into a single call to refresh the GUI, but what about sharing properties between the audio and GUI thread?

I think you’d need to ask, what needs to come from the audio thread? In my mind the audio callback is only ever a consumer of data from the GUI thread, never a producer (at least until/if JUCE supports sample accurate automation). The exception is for any kind of metering, but that should be handled by a double-buffered FIFO anyways and consumed in a timer callback, so there’s not a lot of necessity to signal the GUI from the processor.

That said there’s been some cool discussion around lock-free FIFOs with backpressure in some Rust lang forums, especially for audio applications.

Like what’s an example of some place where you really need to collect some info from the audio callback only some of the time, push it to a FIFO and then signal the GUI that it’s ready?

I meant more like reading GUI parameters in your audio thread

Well, basically it’s all there, because we have C++, but there are couple ways to do it wrong.

My request is, that juce could provide very solid building blocks of doing things right and easier, instead that everybody implements them in a own way and probably wrong.

For example exchanging heavy weight objects in the audio-thread.
There was an example of a release pool, which was promoted here in the forum (if I remember from @timur) . This used a mutex to save the internal array, in the end I didn’t solve the problem because it can cause performance intrusion, instead it created an overhead of source-code.

Or creating synth-voice objects in the audio-thread. Yes there are patterns like creating this objects at another thread, using a lock-free fifo to transport them to the audio-thread.
But if the problem behind it (the memory allocation which shouldn’t affect the audio thread) would be solved correctly, everything would be much easier. No workarounds would be required, just:

SynthVoice* s = new (Juce::RealtimeAllocator->allocate(sizeof(Synthvoice)) SynthVoice();

Another example is lock-free messaging. The audio-thread wants to signalise that there is some kind of overload or whatever, to update the GUI, yes it could set a flag, and then there is a second helper thread to read that flag and translate it into a message, basically a triggerAsyncUpdate() or sendChangeMessage(), the problem is that the current implementation of them can cause performance intrusion.

And solving these kind of problems, is what I like to see in a framework which is specialised in realtime audio.

Definitely a good FR, thanks folks!

2 Likes

I’m currently working on a plug-in that needs to transmit non-trivial data from the audio thread to the GUI thread and vice-versa, since it’s a graph-based pitch editor.
The Audio thread reports the detected pitch to the GUI thread, and when the user does modifications to the output pitch, the GUI thread reports these back to the Audio thread, so both threads have a synchronized state regarding the input and output pitch.
This is certainly an uncommon use case though.

In any case, if you need a lock-free FIFO queue to transmit such data, I can recommend moodycamel::ReaderWriterQueue (which has the advantage of being header-only and standalone) and folly::ProducerConsumerQueue.

1 Like

I also use the lockfree queue from moodycamel with excellent results.

Tried the AbstractFIFO class but I found the separation between logic and data a bit weird.

It follows the aggregation over inheritance principle, but I agree, it has it’s strengths when pushing and pulling arrays of data, rather than individual entries.
That’s why I also wouldn’t use it for pushing and pulling parameter change messages.

It would be awesome to have a real-time compatible allocator and hopefully it would also provide STL allocator compatibility so all STL containers could be used.

This should do the trick for the memory pool and STL container compatible allocator
https://www.boost.org/doc/libs/1_66_0/libs/pool/doc/html/header/boost/pool/pool_alloc_hpp.html

1 Like

While the boost pool allocator does work with standard containers, it also requires a mutex and it is unclear to me how often that gets locked. Depending on that it might be usable or not-recommendable for realtime threads. Have you used it on an audio thread?

Clearly the goal here is to provide a null mutex and assure you that you only access it from the audio thread.
I use my own MemoryPool and dedicated pool std allocator where the pool is given to the container ctor so no experience with this particular one but the rationale is the same. One dedicated pool for the audio thread.