繁体   English   中英

递归与迭代图遍历中的内存利用率

[英]Memory utilization in recursive vs an iterative graph traversal

我已经看了一些像Heapy这样的常用工具来衡量每种遍历技术使用了多少内存,但我不知道他们是否给了我正确的结果。 这是给出上下文的一些代码。

代码只是测量图中唯一节点的数量。 提供了两种遍历技术。 count_bfscount_dfs

import sys
from guppy import hpy
class Graph:
    def __init__(self, key):
        self.key = key       #unique id for a vertex 
        self.connections = []
        self.visited = False 

def count_bfs(start):
    parents = [start]
    children = []
    count = 0
    while parents:
        for ind in parents:
            if not ind.visited:
                count += 1
                ind.visited = True
                for child in ind.connections:
                        children.append(child)

        parents = children
        children = []
    return count

def count_dfs(start):
    if not start.visited:
          start.visited = True
    else:
          return 0

    n = 1
    for connection in start.connections:
        n += count_dfs(connection)
    return n


def construct(file, s=1): 
    """Constructs a Graph using the adjacency matrix given in the file

    :param file: path to the file with the matrix
    :param s: starting node key. Defaults to 1

    :return start vertex of the graph
    """
    d = {}
    f = open(file,'rU')
    size = int(f.readline())
    for x in xrange(1,size+1):
        d[x] = Graph(x)
    start = d[s]
    for i in xrange(0,size):
           l = map(lambda x: int(x), f.readline().split())
           node = l[0]
           for child in l[1:]:
               d[node].connections.append(d[child])
    return start


if __name__ == "__main__":
    s = construct(sys.argv[1])
    #h = hpy()
    print(count_bfs(s))
    #print h.heap()
    s = construct(sys.argv[1])
    #h = hpy()
    print(count_dfs(s))
    #print h.heap()

我想知道两种遍历技术中总内存利用率的不同因素是什么。 count_dfscount_bfs 有人可能会直觉dfs可能很昂贵,因为为每个函数调用创建了一个新堆栈。 如何测量每种遍历技术中的总内存分配?
(评论的) hpy语句是否提供了所需的度量?

带连接的示例文件:

4
1 2 3
2 1 3
3 4 
4

这是一个python问题,使用多少堆栈空间可能比总内存多少更重要。 Cpython的下限为1000帧,因为它与c调用堆栈共享其调用堆栈,而c调用堆栈在大多数地方限制为1兆字节的数量级。 出于这个原因,当递归深度无界时,你几乎应该总是喜欢递归解决方案。

* python的其他实现可能没有这个限制。 cpython和pypy的无堆栈变体具有这种确切的属性

很难准确测量使用多少内存,因为系统实现堆栈帧的方式各不相同。 一般来说,递归算法比迭代算法使用更多的内存,因为每当发生新的函数调用时,每个堆栈帧都必须存储其变量的状态。 考虑动态编程解决方案和递归解决方案之间的区别。 迭代实现算法的运行时要快于递归算法。

如果您真的必须知道代码使用了多少内存,请在调试器(如OllyDbg( http://www.ollydbg.de/ ))中加载软件并计算字节数。 快乐的编码!

对于您的具体问题,我不知道是否会有一个简单的解决方案。 这是因为,图遍历的峰值内存使用量取决于图本身的细节。

对于深度优先遍历,当算法进入最深深度时,将使用最大的用法。 在示例图中,它将遍历1->2->3->4 ,并为每个级别创建一个堆栈帧。 所以当它在4时它已经分配了最多的内存。

对于广度优先遍历,使用的存储器将与每个深度处的节点数量加上下一深度处的子节点数量成比例。 这些值存储在列表中,这可能比堆栈帧更有效。 在该示例中,由于第一个节点连接到所有其他节点,因此它会在第一个步骤[1]->[2,3,4]立即发生。

我确信有一些图表可以通过一次搜索或其他搜索做得更好。

例如,想象一个看起来像链表的图表,其中所有顶点都在一个长链中。 深度优先遍历将具有非常高的峰值内存使用率,因为它将一直递减到链中,为每个级别分配堆栈帧。 广度优先遍历将使用更少的内存,因为它只有一个顶点和一个子节点来跟踪每一步。

现在,将其与深度为2的树形成对比。 也就是说,有一个根元素连接到很多孩子,其中没有一个相互连接。 深度优先遍历在任何给定时间都不会占用太多内存,因为它只需要在必须备份并尝试另一个分支之前遍历两个节点。 另一方面,深度优先遍历将把所有子节点同时放在内存中,这对于一棵大树来说可能会有问题。

您当前的分析代码将找不到所需的峰值内存使用量,因为它只会在您调用heap时查找堆上对象使用的内存。 在你的遍历之前和之后,这可能是相同的。 相反,您需要将分析代码插入遍历函数本身。 我找不到预先构建的guppy包自己尝试,但我认为这个未经测试的代码将起作用:

from guppy import hpy

def count_bfs(start):
    hp = hpy()
    base_mem = hpy.heap().size
    max_mem = 0
    parents = [start]
    children = []
    count = 0
    while parents:
        for ind in parents:
            if not ind.visited:
                count += 1
                ind.visited = True
                for child in ind.connections:
                        children.append(child)
        mem = hpy.heap().size - base_mem
        if mem > max_mem:
            max_mem = mem
        parents = children
        children = []
    return count, max_mem

def count_dfs(start, hp=hpy(), base_mem=None):
    if base_mem is None:
        base_mem = hp.heap().size

    if not start.visited:
          start.visited = True
    else:
          return 0, hp.heap().size - base_mem

    n = 1
    max_mem = 0
    for connection in start.connections:
        c, mem = count_dfs(connection, base_mem)
        if mem > max_mem:
            max_mem = mem
        n += c
    return n, max_mem

两个遍历函数现在都返回一个(count, max-memory-used)元组。 您可以在各种图表上试用它们,看看它们之间的区别。

在这两者中,如果大多数遍历最终击中大部分图形,则深度优先使用较少的内存。

当目标靠近起始节点时,或者当节点数量没有快速增加时,广度优先可以更好,因此代码中的父/子数组保持较小(例如,另一个答案提到链表为最坏情况)对于DFS)。

如果您正在搜索的图形是空间数据,或者具有所谓的“可接受的启发式”,则A *是另一种非常好的算法: http//en.wikipedia.org/wiki/A_star

然而,过早优化是万恶之源。 查看您要使用的实际数据; 如果它适合合理的内存量,并且搜索在合理的时间内运行,那么使用哪种算法并不重要。 注意,什么是“合理的”取决于您使用它的应用程序以及将运行它的硬件上的资源量。

对于使用描述它的标准数据结构迭代实现的搜索顺序(BFS的队列,DFS的堆栈),我可以构建一个简单地使用O(n)内存的图。 对于BFS来说,它是一个n星,对于DFS来说,它是一个n链。 我不相信它们中的任何一个都可以为一般情况实现做得更好,所以这也给出了最大内存使用量的Omega(n)下限。 因此,通过每个的有效实现,它通常应该是一个洗涤。

现在,如果您的输入图形具有某些特征,这些特征使它们偏向于其中一个极端或另一个极端,那么这可能会让您决定在实践中使用哪个极端。

暂无
暂无

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

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