I non-naively converted the buffer to Swift, for reference. It is translated as literally as possible from C++. The problem was allocating a Swift array inside a class, which is already reference counted. Either change RingBuffer
to struct, or change buffer
to a pointer that deallocates upon RingBuffer.deinit
. I chose the latter. Second, force-inline put
and get
if you're exporting this type as part of a public library. You don't want the overhead of extra function calls when it's obvious the assembly of RingBuffer.put
and RingBuffer.get
will be tiny.
Putting that aside, the main point is you should avoid Swift ARC types like Array and Dictionary inside an audio kernel function. The 2016 WWDC video (37:50 - 42:25) described a render function made entirely in Swift, using just pointers. This function touches only upon RAM reads/writes and ALU instructions, with all memory pre-allocated and no calls into swift_retain
. It is easy to restrict yourself to these kinds of ASM instructions in plain C, which is probably why Apple recommended to use exclusively C/C++ for audio until 2016.
But to be helpful to someone trying to learn Swift, we can teach them about these performance characteristics of Swift. They might not find it super problematic. For audio code, you need to think like a C programmer whether you're writing the kernel in Swift or C++. The statement above gives the connotation that you "the reader" can't learn how to effectively work within this mindset. And that if they do, there's no way it's sustainable in the long term.
To restate the concerns from that paragraph:
- When working with audio, avoid synchronization primitives. This can be managed by pre-allocating your memory into raw pointers. In real-time code, only work with memory backed by those pointers.
- Experimental, custom code will bring glitches. This is something all programmers face, in every context. Bugs are no fun, but there are many ways to search for bugs or performance regressions. Swift package tests make it easy to test whether a code base runs correctly, but other options are also available.
- The compiler's internals can change at any time. In general, the compiler gets faster over time. If a section of code runs fast now, it will probably run fast in the future. New features like explicit stack allocations are subject to change, but the documentation/forums are good at explaining these areas for change when they do appear.