【发布时间】:2011-08-07 22:25:30
【问题描述】:
我有一个类使用递归回溯算法解决精确覆盖问题。最初,我使用在初始化期间传递给对象的回调函数来实现该类。每当找到解决方案时都会调用此回调。在查看其他人对同一问题的实现时,我看到他们正在使用 yield 语句来传递解决方案,换句话说,他们的代码是一个 python 生成器。我认为这是一个有趣的想法,所以我制作了一个新版本的课程来使用产量。然后我在两个版本之间进行了比较测试,令我惊讶的是,我发现生成器版本的运行速度比回调版本慢 5 倍。请注意,除了为回调切换 yield 之外,代码是相同的。
这里发生了什么?我推测,因为生成器需要在屈服之前保存状态信息,然后在下一次调用时重新启动时恢复该状态,正是这种保存/恢复使生成器版本运行得这么慢。如果是这种情况,生成器需要保存和恢复多少状态信息?
python 专家有什么想法吗?
--编辑于太平洋夏令时间 7:40
这是使用 yield 的求解器代码。将下面的第一个 yield 替换为对回调函数的调用,并将以下循环与第二个 yield 更改为仅递归调用以解决此代码的原始版本。
def solve(self):
for tp in self.pieces:
if self.inuse[tp.name]: continue
self.inuse[tp.name] = True
while tp.next_orientation() is not None:
if tp.insert_piece():
self.n_trials += 1
self.pieces_in += 1
self.free_cells -= tp.size
if self.pieces_in == len(self.pieces) or self.free_cells == 0:
self.solutions += 1
self.haveSolution = True
yield True
self.haveSolution = False
else:
self.table.next_base_square()
for tf in self.solve():
yield tf
tp.remove_piece()
self.pieces_in -= 1
self.table.set_base_square(tp.base_square)
self.free_cells += tp.size
self.inuse[tp.name] = False
tp.reset_orientation()
调用求解器的邮件循环(当然是在初始化之后)是
start_time = time.time()
for tf in s.solve():
printit(s)
end_time = time.time()
delta_time = end_time - start_time
在回调版本中,只需一次调用即可解决循环。
【问题讨论】:
-
请提供一个(简化但完整的)示例以及您如何测量时间。
-
yield来自递归函数听起来需要额外的for循环才能将结果传递给调用者,不是吗?也许你的意思是协程,用send传递结果? -
通常可以找到多少种解决方案? (您的产量是很多还是相对较少?)
-
作为记录,我的两个小测试(ideone.com/7XCro,ideone.com/VuKRn,ideone.com/DhTJF)似乎表明收益率和回调之间的性能差异不大,回调缓慢改善,而它做更多的工作。我很想看到 OP 的反例。
-
我为我的代码的生成器版本添加了相关代码。我还展示了 main 如何调用求解器以及我是如何进行计时的。关于 FogleBirds 问题,两个版本都找到了完全相同的一组解决方案,对于给定的问题是正确的。
标签: python profiling generator