简体   繁体   English

为什么这个Fizz Buzz生成器比这个Fizz Buzz Iterator类快得多?

[英]Why is this Fizz Buzz generator significantly faster than this Fizz Buzz Iterator class?

After learning about iterator class methods and generators, I tested the performance characteristics of simple Fizz Buzz solutions utilizing each idiom: 在了解了迭代器类的方法和生成器之后,我使用每种习语测试了简单的Fizz Buzz解决方案的性能特征:

>>> from timeit import timeit
>>> timeit('tuple(fizzbuzz.FizzBuzzIterator(10))', 'import fizzbuzz')
13.281935930252075
>>> timeit('tuple(fizzbuzz.fizz_buzz_generator(10))', 'import fizzbuzz')
7.619534015655518

According to timeit the generator function is about 1¾ times faster than the iterator class. timeit发电机功能比iterator类快了1.75倍

My question again: Why is this Fizz Buzz generator significantly faster than this Fizz Buzz Iterator class? 再问我一个问题:为什么这个Fizz Buzz生成器比这个Fizz Buzz Iterator类快得多?

Fizz Buzz iterator class Fizz Buzz迭代器类

class FizzBuzzIterator:

    def __init__(self, low, high=None):
        if high is None:
            self.high = low
            self.current = 1
        else:
            self.high = high
            self.current = max(low, 1)

    def __iter__(self):
        return self

    def next(self):
        if self.current > self.high:
            raise StopIteration
        else:
            c = self.current
            self.current += 1
            if (c % 5 + c % 3) == 0:
                return 'FizzBuzz'
            elif c % 5 == 0:
                return 'Buzz'
            elif c % 3 == 0:
                return 'Fizz'
            else:
                return str(c)

Fizz Buzz generator function 嘶嘶声发生器功能

def fizz_buzz_generator(low, high=None):
    if high is None:
        high = low
        cur = 1
    else:
        cur = max(low, 1)
    while cur <= high:
        c = cur
        cur += 1
        if (c % 5 + c % 3) == 0:
            yield 'FizzBuzz'
        elif c % 5 == 0:
            yield 'Buzz'
        elif c % 3 == 0:
            yield 'Fizz'
        else:
            yield str(c)

Because apparently, generators are implemented more efficiently than iterators. 因为显然,生成器比迭代器更有效地实现。

Your first solution has the following interesting characteristics: 您的第一个解决方案具有以下有趣的特征:

  1. It uses objects. 它使用对象。
  2. For an iteration over n numbers, 3 + n methods are called and 2 + 4·n attributes are accessed, which are both potentially slow operations. 对于n个数字上的迭代,将调用3 + n个方法并访问2 + 4·n个属性,它们都是潜在的缓慢操作。
  3. Exceptions are used for control flow. 异常用于控制流。

The second solution does none of these, instead it yield s which means that the language runtime performs the heavy lifting. 第二种解决方案不执行任何操作,而是yield s,这意味着语言运行库会执行繁重的工作。 As the runtime is usually implemented in C, this can be far more optimized than the very high-level code of your first solution. 由于运行时通常是用C实现的,因此与第一个解决方案的高级代码相比,它的优化程度更高。

Next, you should consider what you are actually benchmarking. 接下来,你应该考虑实际基准。 For a good benchmark you should choose different input sizes n and watch how the two solutions compare at different scales. 为了获得良好的基准,您应该选择不同的输入大小n,并观察两种解决方案在不同规模下的比较情况。

  • For very small n , we expect the initalization cost to dominate. 对于非常小的n ,我们预计初始化成本将占主导地位。 This is consistent with your results, as performing a function call is less expensive than creating an object. 这与您的结果一致,因为执行函数调用比创建对象便宜。
  • For larger n , we expect the features of the algorithm to dominate. 对于较大的n ,我们期望算法的功能起主导作用。 As the algorithms are exactly the same, the graphs should have the same shape. 由于算法完全相同,因此图应具有相同的形状。 But per iteration, the first solution has a much higher cost (four attribute accesses, one method call). 但是对于每次迭代,第一个解决方案的成本要高得多(四个属性访问,一个方法调用)。 The two solutions would then be graphs with slightly different slopes. 这两个解决方案将是斜率略有不同的图。 The exact relation of the per-iteration costs can only be assessed by obtaining a large data set of many timings for many input sizes n , and then fitting a function to that data. 每次迭代成本的确切关系只能通过为许多输入大小n获得大量包含多个时间的大型数据集,然后对该数据拟合函数来进行评估。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM