self and time in installTap

Hi, I have a rather mundane question about the mechanics of Swift when implementing blocks that may run on a separate thread, such as the one in installTap(onBus:)

For the time being it isn't necessary to make a full-blown AudioUnit out of what I'm doing, I just need to listen for incoming audio and process it slightly. The processing involves some buffering, filtering, hence these are kept as classes, such that they remember state, past samples, etc. My question is then, when I access "self" in this listening tap block, does this slow the thread signficantly? If so, tricks to keep state across taps are very welcome 🙂


An aside to this topic; how does one ensure that the block finishes before the next? My first thought was a check sum and invalidate the result of one block if the check fails, but does the API provide some neat trick for this?

Accepted Reply

>> does this slow the thread signficantly?


No, unless you have a concern that I'm overlooking. The tiny overhead involved in capturing "self" (and any other variable you reference inside the closure) is paid at the time the closure is created, not during execution. At that point, "self" is just a pointer, and there's no expensive thunking involved in indirecting through it. Note that code in your block (since it's using ARC) might retain and release references, and there's a thread safety mechanism involved (some kind of lock), so if an extremely large numbers of closures are maxing out CPUs concurrently, you may see the effects of some contention, but it's sort of a different issue.


>> how does one ensure that the block finishes before the next?


I'm not sure I understand the question. Before the next what? Block, obviously, but are you asking about blocks that might be in-flight simultaneously, or about starting something new because you've just finished with a previous block? How does a checksum sequentialize block usage?

Replies

>> does this slow the thread signficantly?


No, unless you have a concern that I'm overlooking. The tiny overhead involved in capturing "self" (and any other variable you reference inside the closure) is paid at the time the closure is created, not during execution. At that point, "self" is just a pointer, and there's no expensive thunking involved in indirecting through it. Note that code in your block (since it's using ARC) might retain and release references, and there's a thread safety mechanism involved (some kind of lock), so if an extremely large numbers of closures are maxing out CPUs concurrently, you may see the effects of some contention, but it's sort of a different issue.


>> how does one ensure that the block finishes before the next?


I'm not sure I understand the question. Before the next what? Block, obviously, but are you asking about blocks that might be in-flight simultaneously, or about starting something new because you've just finished with a previous block? How does a checksum sequentialize block usage?

Thank you, that's a great answer for the "self" part, I'll stick to that then — much cleaner and simpler! 🙂


What I mean by ensuring the block has finished before the next is that I did encounter an issue, while implementing a naive approach to the problem I'm solving, just to get it to work first. The code was naturally not optimised for performance, and I got a malloc-error, which to me suggests that the data pointer was released by Swift before the block finished analysing the data (i.e. the next block was probably called) — my own code didn't allocate or release anything explicitly, so this surely must have been the case. Therefore, if I deploy on older devices, I'd like to ensure that if any tap block fails to complete in time for the next block, I'd like to handle that, just in case that happens. Of course, I'll be optimising the code before deployment, but it would be nice to have a safety guard on this issue. Does that make more sense? 🙂


The checksum was in case the installTap implementation merely overwrites the buffer it uses to pass the data to the block. But thinking about it, if the malloc error could occur, then that pointer is no longer valid, and so a checksum would trigger the same issue :/

I don't think there's any good way to bulletproof memory usage without a clearer narrative of what AVFoundation is doing with the buffers and when. It doesn't seem likely that merely failing to finish processing one buffer would directly lead to a crash. If you're seeing an error in terms of malloc, then you probably aren't dealing with reference counted memory, so it's a different kind of problem. Or, depending on what the error is, you may have overrun a buffer and clobbered memory, or something like that.


There will be some kind of API contract implied by the AVFoundation documentation for the classes you're using. That's probably the place to start. But it's also important to understand the nature of your crash, so that you don't waste time looking for the problem in the wrong place.

If you installTap(onBus:) , and don't want to have hiccups or other problems, it might be best to follow the real-time audio context rules: no memory allocation or deallocation or other expensive processing inside the audio tap callback. Pass those out to another sequential GCD thread to do.