为什么使用Python生成器来遍历二叉树要慢得多?

时间:2014-07-25 18:17:29

标签: python recursion generator pypy

我有一个二叉树,节点与数据交互。我最初实现了标准的邮政订单递归遍历。

def visit_rec(self, node, data):
    if node:
        self.visit_rec(node.left, data)
        self.visit_rec(node.right, data)

        node.do_stuff(data)

我认为我可以通过使用生成器来改进它,以便我可以使用相同的遍历方法用于其他用途,而不必不断地传递相同的数据。此实现如下所示。

def visit_rec_gen(self, node):
    if node:
        for n in self.visit_rec_gen(node.left):
                yield n
        for n in self.visit_rec_gen(node.right):
                yield n

        yield node

for node in self.visit_rec_gen():
    node.do_stuff(data)

然而,这比以前的版本(~50s到~17s)慢得多,并且使用了更多的内存。我的发电机功能版本有错吗?我更喜欢使用这种方法,但不是以牺牲性能为代价。

编辑:我最初应该提到的一点是,这些结果是在PyPy 2.3.1下获得的,而不是标准的CPython。

3 个答案:

答案 0 :(得分:6)

在PyPy上,函数调用比生成器或迭代器更加优化。

在PyPy中有很多不同的性能特征(例如,PyPy的itertools.islice()执行不正常)。

通过衡量性能来确定哪种方式最快,你做的是正确的。

另请注意,PyPy具有显示生成的代码的工具,因此您可以更详细地回答“它做什么”的问题。当然,“为什么会这样做”的问题在答案中有一个人的组成部分,涉及实施方便或实施者的倾向。

答案 1 :(得分:3)

如果你正在使用python3.3,yield from语句被优化为比为了屈服而迭代更快:

def visit_rec_gen(self, node):
    if node:
        yield from self.visit_rec_gen(node.left)
        yield from self.visit_rec_gen(node.right)
        yield node

答案 2 :(得分:2)

由于使用发电机的现实,生成方法效率较低。但是,您可以通过基于回调的系统获得非生成器的大部分效率,从而获得生成器方法的灵活性。

# NOTE that this should be a method on Node, not Tree
def apply_to_children_and_self(self, func, *args, **kwargs):
    if self.left:
        self.left.apply_to_children_and_self(func, *args, **kwargs)
    if self.right:
        self.right.apply_to_children_and_self(func, *args, **kwargs)
    func(self, *args, **kwargs)

...

head.apply_to_children_and_self(Node.do_stuff, data)