Fixed-size array vs Dynamically allocated array for audio callback buffers.
-
- KVRer
- Topic Starter
- 2 posts since 13 Dec, 2014
Hey all, this is my first post here - KVR seems to be one of the most thriving communities for audio dev, I'm looking forward to joining in on the discussion
Anyway, to business:
I'm curious about the performance implications of using a fixed-size array for an audio callback buffer rather than a dynamic array (like the c++ `vector`). All audio frameworks that I've come across seem to use a dynamic array, which I can understand for programs who may need to change their buffer size during runtime, or allocate many buffers at once in the same process.
I'm currently working on a project where 1. I do not need to change the buffer-size at run-time and 2. There will be no more than two buffers allocated on my audio thread at any one time. For this reason I've been having a go at using a fixed size array instead, as I figure having the compiler prepare the allocation on a stack frame would be *much* faster than dynamic allocation (right?) . I'm using bindings to PortAudio (I'm developing my project in Rust) as my backend for driving the audio thread and it seems to be handling the fixed size array without problems. The audio thread seems to run at *least* as fast but I'm finding it hard to bench mark due to the current state of my project so I thought I'd ask:
What are your experiences with using fixed-size arrays for audio calback buffers? Do you have any examples of where they are used in the wild? Is it reasonable to expect a performance increase using a fixed-size array instead of a dynamic array/vector?
Also, would using a fixed-size array allow for much smaller buffer sizes (i.e. something like 32 samples instead of something like the stereotypical 256 samples) without any performance implications? Or are there other reasons larger buffer sizes are used other than to reduce the number of dynamic allocations?
Thanks
Anyway, to business:
I'm curious about the performance implications of using a fixed-size array for an audio callback buffer rather than a dynamic array (like the c++ `vector`). All audio frameworks that I've come across seem to use a dynamic array, which I can understand for programs who may need to change their buffer size during runtime, or allocate many buffers at once in the same process.
I'm currently working on a project where 1. I do not need to change the buffer-size at run-time and 2. There will be no more than two buffers allocated on my audio thread at any one time. For this reason I've been having a go at using a fixed size array instead, as I figure having the compiler prepare the allocation on a stack frame would be *much* faster than dynamic allocation (right?) . I'm using bindings to PortAudio (I'm developing my project in Rust) as my backend for driving the audio thread and it seems to be handling the fixed size array without problems. The audio thread seems to run at *least* as fast but I'm finding it hard to bench mark due to the current state of my project so I thought I'd ask:
What are your experiences with using fixed-size arrays for audio calback buffers? Do you have any examples of where they are used in the wild? Is it reasonable to expect a performance increase using a fixed-size array instead of a dynamic array/vector?
Also, would using a fixed-size array allow for much smaller buffer sizes (i.e. something like 32 samples instead of something like the stereotypical 256 samples) without any performance implications? Or are there other reasons larger buffer sizes are used other than to reduce the number of dynamic allocations?
Thanks
- KVRAF
- 7890 posts since 12 Feb, 2006 from Helsinki, Finland
Whether you allocate a buffer as fixed or dynamic size makes little difference, as long as you don't do such allocation while the audio is running: the act of allocating or deallocating a buffer in audio thread can cause a drop-out, but rest of the time it doesn't really matter. Some variants of dynamic arrays (including some implementations of std::vector) do bound checking for array indexes, which you probably don't want, so using a custom RAII array might be preferable over std::vector (and similarly in a language like Rust you might want to make your inner loops "unsafe" to get rid of such overhead; no personal experience with Rust, but I saw a presentation about it).
The main reason audio frameworks do dynamic allocation is just so they can deal with whatever blocksize is requested by the upstream components (eg. the host for a plugin, audio driver for an application, etc). On the other hand, if you want to split longer blocks into some internal maximum block size (or possibly process in completely fixed buffers) then you can certainly allocate fixed size buffers as well. In any case, performance it really doesn't make much difference.
The main reason audio frameworks do dynamic allocation is just so they can deal with whatever blocksize is requested by the upstream components (eg. the host for a plugin, audio driver for an application, etc). On the other hand, if you want to split longer blocks into some internal maximum block size (or possibly process in completely fixed buffers) then you can certainly allocate fixed size buffers as well. In any case, performance it really doesn't make much difference.
-
- KVRian
- 1265 posts since 9 Sep, 2005 from Oulu, Finland
I doubt that is true for the operator [] in any sane modern implementation of std::vector when doing release builds. std::vector::at() is by standard required to do the bounds checking, though.mystran wrote:Some variants of dynamic arrays (including some implementations of std::vector)
- KVRAF
- 7890 posts since 12 Feb, 2006 from Helsinki, Finland
Probably true, but I would make too many assumptions about sanity of STL implementations. Many of them do check for debug builds, which can be somewhat of a pain (if you are trying to debug something else) when debug builds have a tendency of running ridiculously slow even without such overhead.Xenakios wrote:I doubt that is true for the operator [] in any sane modern implementation of std::vector when doing release builds. std::vector::at() is by standard required to do the bounds checking, though.mystran wrote:Some variants of dynamic arrays (including some implementations of std::vector)
-
- KVRian
- 1265 posts since 9 Sep, 2005 from Oulu, Finland
Real Men(tm) don't do debug builds! (Actually I am half serious...Since the product that is going to be released to the public isn't going to be debug build anyway, I personally tend to be working with release builds most of the time when developing, so I will be working with realistic stability and performance scenarios...Of course sometimes I will spend some fun time with debug builds and the debugger.)mystran wrote:debug builds
-
- KVRAF
- 7400 posts since 17 Feb, 2005
If you need to declare a new vector (can't just use the host's) AND copy the audio buffer into it, then it's best to use a heap allocated vector for compatibility. Although I don't think I've seen a host which gives plugins blocks larger than 16384 per channel.
- KVRAF
- 7890 posts since 12 Feb, 2006 from Helsinki, Finland
To be honest, I might be over-thinking things and using std::vector might be just as well as long as you just make sure to initialize it to fixed size once when the blocksize changes (and then cache it between call-backs). I just tend to personally prefer custom classes that have exactly those methods that you are supposed to use and nothing else (eg. no copy constructors allowed, no implicit reallocation, and so on) just to keep the code as clear as possible.
-
- KVRian
- 1265 posts since 9 Sep, 2005 from Oulu, Finland
Sure, that's a reasonable approach in many cases!mystran wrote:have exactly those methods that you are supposed to use and nothing else (eg. no copy constructors allowed, no implicit reallocation, and so on) just to keep the code as clear as possible.
-
- KVRian
- 1379 posts since 26 Apr, 2004 from UK
MSVC implementation does bound check at least in debug mode.Xenakios wrote:I doubt that is true for the operator [] in any sane modern implementation of std::vector when doing release builds. std::vector::at() is by standard required to do the bounds checking, though.mystran wrote:Some variants of dynamic arrays (including some implementations of std::vector)
Reallocating a vector is not problematic if the size is more or less constant, as if you allocate a smaller chunk, the actual memory will not be reallocated (which is why sometimes to really clear a vector, you would swap your old vector with a new empty one).
-
- KVRian
- 1265 posts since 9 Sep, 2005 from Oulu, Finland
So what if it does bounds checking in debug mode? Are you distributing debug builds that have to be run under the debugger to end users...? (I know in extreme cases debug builds run under the debugger so slow already you don't want any extra slowness, but then it is probably a good time to reflect on why are you developing running debug builds anyway...What exactly does the debugger help with in performance sensitive audio DSP code?)Miles1981 wrote:MSVC implementation does bound check at least in debug mode.Xenakios wrote:I doubt that is true for the operator [] in any sane modern implementation of std::vector when doing release builds. std::vector::at() is by standard required to do the bounds checking, though.mystran wrote:Some variants of dynamic arrays (including some implementations of std::vector)