发电机的效率

Efficiency of generators

我正在编写一些软件来创建复杂波形(实际上是声波)作为数组。从一些原始波形(正弦波等)开始,会有将它们组合起来创建更复杂波的函数,还有更多将这些波组合起来的函数,等等。

它可能看起来像:

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

但要复杂得多。

这样做的一种方法是让每个函数成为一个生成器,这样整个函数树每个元素执行一次,每个生成器每次都产生一个值。

数组可能有几百万个元素,函数树很容易达到 10 层。发电机这样做会不会效率低得离谱?

另一种方法是让每个函数都创建一个 return 整个数组。这可能会更有效,但也有缺点(实现更混乱,计算结束前没有可用的结果,可能会使用大量内存)。

他们总是说你不应该尝试猜测 Python 效率,但在这种情况下生成器会花费很长时间吗?

在我看来,生成器非常适合这项任务。

一些信号的时间有限(如包络线或斜坡),但其他一些信号是无限的(如振荡器)。

使用生成器你不必担心这方面的问题,因为 - 就像 zip() 函数一样 - 将振荡器与包络相结合(例如相乘)的函数只会消耗来自振荡器生成器的有限数量的项目,因为至少有一个生成器可以生成有限数量的样本。

然而,使用生成器非常优雅和 pythonic。

回想一下这样的生成器:

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

只是 class 的语法糖,如下所示:

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()

因此性能开销并不比您可以手工制作的任何其他解决方案(如块处理,通常用于 DSP 算法)。

如果不是生成单个样本,而是生成样本块(例如一次 1024 个样本),也许您可​​以获得一些效率。

生成器是惰性序列。当您的序列可能很长时,只要您可以分段操作(按元素或在合理大小的块上),它们就非常适合使用。

这往往会减少您的内存使用峰值。只是不要通过将序列的所有元素存储在某处来破坏它。