Efficiency of python generators

I am writing some software to create a complex waveform (actually a sound wave) as an array. Starting with some primitive waveforms (sine waves, etc.), functions will be performed that combine them to create more complex waves and even more functions that combine these waves, etc.

It might look like this:

f(mult(sine(), env(square(), ramp()))

      

but much more complicated.

One way to do this is to make each function a generator so that the entire function tree is executed once for each item with each generator, getting one value each time.

An array can have several million elements, and a function tree can be 10 deep. Will generators be ridiculously inefficient for this?

An alternative would be to create and return the entire array for each function. This would apparently be more efficient, but it would have disadvantages (a messy implementation, with no results until the end of the calculation, could use a lot of memory).

They always say that you shouldn't try to guess the efficiency of Python, but will generators take a long time in this case?

+3


source to share


2 answers


In my opinion, generators are suitable for this task.

Some signals have finite time (for example, envelope or ramp), but some other signals are infinite (for example, oscillators).

When using generators, you don't have to worry about this aspect, because as a function zip()

- the function of combining (e.g. multiplying) an oscillator with an envelope consumes only a finite number of elements from the generator gen, because there is at least one generator that gives a finite number of samples ...

However, using generators is very elegant and pythonic.

Recall that such a generator:



def sine(freq):
    phase = 0.0
    while True:
        yield math.sin(phase)
        phase += samplerate/freq

      

is just syntactic sugar for a class like this:

class sine:
    def __init__(self, freq):
        self.freq = freq
        self.phase = 0.0

    def __iter__(self):
        return self

    def __next__(self):
        v = math.sin(self.phase)
        self.phase += samplerate/freq
        return v
        # for this infinite gen we never raise StopIteration()

      

so the performance overhead is not much more than any other solution you can use (like block processing commonly used in DSP algorithms).

Perhaps you could get some efficiency if instead of giving individual samples, you get blocks of samples (for example, 1024 samples at a time).

+2


source


Generators are lazy sequences. They are ideal for use when you have sequences that can be very long if you can work piecewise (both element and size).



This will result in less peak memory usage. Just don't destroy so that you can store all the elements of the sequence somewhere later.

+2


source







All Articles