簡體   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