【问题标题】:Why does this recursive function rapidly increase memory use?为什么这个递归函数会迅速增加内存使用?
【发布时间】:2020-01-24 17:11:53
【问题描述】:

我在 Python 中编写了以下递归函数来索引 liststr 值(可能在列表中多次重复相同的值)。该函数接受list 并返回dict,其中字典的每个条目是项目列表(str)和对应的int 索引。

def make_indices(entries):
    def _make_indices(ent, idxs, idx):
        if not ent:
            return idxs
        else:
            _make_indices(ent[1:], idxs, idx) if ent[0] in idxs \
                else _make_indices(ent[1:], dict({ent[0]: idx}, **idxs), idx+1)
    return _make_indices(entries, {}, 0)

我认为这将是一个优雅的解决方案,但它的内存使用会随着列表的长度而迅速增加。有人能解释导致这种过度内存使用的确切原因吗?

【问题讨论】:

  • 我的 Python 很弱,所以这主要是一个猜测——你是否在每个递归步骤中都复制列表/字典?如果是这样,您必须等到整个算法停止以清除所有这些,同时您将持有一个列表的多个副本或至少其中的一部分。这可能会有O(n!) 的空间复杂度。
  • 您是否看到堆栈溢出或过度使用内存?这里可能存在二次内存消耗问题,因为在递归结束之前所有临时对象都没有从堆栈中清除(以及构建所有这些临时对象的二次运行时问题),但您可能会在此之前遇到堆栈溢出一个大问题。
  • 另外你忘了returnelse分支,所以这个函数通常会返回None
  • @user2357112supportsMonica 不错!谢谢。我确实同意在代码中的某处进行了复制,但我想知道在函数的每次递归调用中复制的究竟是什么使内存使用量增长得如此之快。

标签: python recursion memory-management


【解决方案1】:

对列表ent[1:] 进行切片将导致切片部分的新分配浅表副本。此外,由于 Python 没有优化尾端递归,您会处于这样一种情况,即您创建的每个切片都将保持分配状态,直到外部调用终止。

尝试调用ent.pop(0) 来删除列表的第一个元素,然后将列表作为ent 传递而不进行切片。这样,就不需要新的分配了

【讨论】:

  • 关于ent[1:] 的有趣点。我可以看到它本身如何导致内存使用量的快速增加。我知道 Python 中缺乏尾递归优化,但没想到这会导致内存使用出现如此大的问题。将尝试ent.pop(0) 建议进行比较。
  • @user1953384 尾递归点总是有一些与之相关的内存成本,因为它需要为每次调用分配堆栈。但是,通过在每个深度分配新空间会增加成本。在函数作用域终止之前,局部变量的值不能被解除分配(除非您明确地del 或重新分配名称),并且如果没有尾递归优化,所有函数作用域将同时保持打开状态直到最终返回。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多