I am writing an audio engine in mojo. FYI - mojo is great for dsp!
Audio graphs run at 48000+ times per second more or less, so I am trying to write efficient code this isn’t constantly allocating and destroying memory.
Question 1: This is a question about memory allocation and the efficiency of different approaches to using variables in a function. The following are 3 different versions of the same one-pole filter function that exist inside a struct. I would like to know which of the following is the best approach when the function is being called over an over again many thousands of times per second:
# new sample gets created in the fn and returned
fn next0(
mut self, sample: SIMD[DType.float64, N], coef: SIMD[DType.float64, N]
) -> SIMD[DType.float64, N]:
loc_samp = (1 - abs(coef)) * sample + coef * self.last_samp
self.last_samp = loc_samp
return loc_samp
# sample gets mutated in the fn and returned - not sure if this achieves anything
fn next1(
mut self, mut sample: SIMD[DType.float64, N], coef: SIMD[DType.float64, N]
) -> SIMD[DType.float64, N]:
sample = (1 - abs(coef)) * sample + coef * self.last_samp
self.last_samp = sample
return sample
# sample just gets mutated with no return. no variable declaration at all.
fn next2(
mut self, mut sample: SIMD[DType.float64, N], coef: SIMD[DType.float64, N]
):
sample = (1 - abs(coef)) * sample + coef * self.last_samp
self.last_samp = sample
The ideal approach would get me the effect of next2 with the syntax of next0. What I want to do is just mutate the sample, but I would also prefer to call the function this way:
sample = one_pole.next1(sample, 0.99)
# vs
one_pole.next2(sample, 0.99)
next2 does what I want, but the syntax is inconsistent with regular mojo code.
Is there a performance cost in next0 vs next2 or am I overthinking this? Does next1 let me have my cake and eat it too or is it achieving nothing (is the sample returned as a copy no matter if it is mutable)?
Question 2: Inside a function of a struct which is called like above (audio-rate at 48K per second), is it more performant to declare a variable (SIMD values - floats and ints and such) in the fn of the struct or to use a struct variable, so:
fn next(...):
temp: Float64 = whatever
# vs
fn next(...):
self.temp = whatever
Thanks,
Sam