2016-09-22 27 views
0

編輯:沒關係,我只是完全愚蠢。隱藏引用導致大量內存使用的函數參數?

我碰到與小串遞歸代碼來了,這裏是它的本質加上我的測試的東西:

def f(s): 
    if len(s) == 2**20: 
     input('check your memory usage again') 
    else: 
     f(s[1:]) 
input('check your memory usage, then press enter') 
f('a' * (2**20 + 500)) 

通話之前,我的Python過程大約需要9 MB(通過Windows任務管理器作爲託運) 。在〜1MB字符串的500個級別之後,它的大小約爲513 MB。毫不奇怪,因爲每個調用級別在其變量s中仍然保留其字符串。

但我試圖通過更換的參考字符串到新的字符串的引用來解決它,它仍然上升到513 MB:

def f(s): 
    if len(s) == 2**20: 
     input('check your memory usage again') 
    else: 
     s = s[1:] 
     f(s) 
input('check your memory usage, then press enter') 
f('a' * (2**20 + 500)) 

爲什麼不,讓熄滅記憶?字符串甚至只是變小,所以後來的字符串很容易適應早期字符串的空間。有沒有隱藏額外的字符串引用或發生了什麼?

我已經預料到這樣的表現,只上升到10 MB(1 MB的變化,符合市場預期,而老的字符串仍然存在,因爲新的字符串建):

input('check your memory usage, then press enter') 
s = 'a' * (2**20 + 500) 
while len(s) != 2**20: 
    s = s[1:] 
input('check your memory usage again') 

(不要介意窮人的時間複雜性,順便說一句,我知道,不要打擾。)

+0

我看到兩者之間沒有什麼區別,說實話。 –

+1

@StefanPochmann,如果你改用'del s [0]'會發生什麼? – BallpointBen

+0

@James然後我得到'TypeError:'str'對象不支持項目刪除'。你沒有明白嗎?你使用的是什麼Python版本? –

回答

2

你的函數是遞歸的,所以當你調用f()時,你的當前幀被放到一個堆棧,並創建一個新的。所以基本上每個函數調用都會引用它創建的新字符串以傳遞給下一個調用。

爲了說明疊層

import traceback 

def recursive(x): 
    if x: 
     recursive(x[1:]) 
    else: 
     traceback.print_stack() 

recursive('abc') 

給出

$ python tmp.py 
    File "tmp.py", line 10, in <module> 
    recursive('abc') 
    File "tmp.py", line 5, in recursive 
    recursive(x[1:]) 
    File "tmp.py", line 5, in recursive 
    recursive(x[1:]) 
    File "tmp.py", line 5, in recursive 
    recursive(x[1:]) 
    File "tmp.py", line 7, in recursive 
    traceback.print_stack() 

何時recursive()返回最終呼叫,則返回回到它上面的下一個呼叫,其仍具有參照x

But I tried to fix it by replacing the reference to the string with a reference to the new string and it still goes up to 513 MB

那麼你在當前的功能的確被調用,但調用它仍具有參考什麼傳入的功能。例如,

def foo(x): 
    print "foo1", locals() 
    bar(x) 
    print "foo2", locals() 

def bar(x): 
    print "bar1", locals() 
    x = "something else" 
    print "bar2", locals() 

foo('original thing') 

foo()被調用時,它通過串'original thing'bar()。而且即使bar()則擺脫了參考的,上面foo()當前呼叫仍然具有參考

$ python tmp_x.py 
foo1 {'x': 'original thing'} 
bar1 {'x': 'original thing'} 
bar2 {'x': 'something else'} 
foo2 {'x': 'original thing'} 

我希望說明它。我在關於堆棧幀的第一個陳述中有點模糊。

+1

謝謝。我只是在那裏腦筋急轉彎。如果它更快地到達這一點,我會接受你的答案,現在我真的更喜歡我的答案。你介意在你的頂部添加它還是類似的東西? –

+0

@StefanPochmann不夠公平,我喜歡華夫餅!我添加了一些更直接的信息。 –

+0

嗯,我猜這也很難說出我在想什麼,以及對我說什麼:-)。我已經接受了你的答案,因爲雖然我自己的答案直接解決了我錯誤的想法,但這是我錯誤的想法,所以我的答案可能不適用於其他人,你的答案可能總體上更好...... –

1

Are there hidden additional references to the strings somewhere or what is going on

好,每個功能都有它的字符串的引用,而它在棧上,所以s = s[1:]仍然要保持s[1:]在未來的函數調用。經過500次遞歸調用後,每次只複製1個字符的事實對於每次傳遞的大約2 ** 20個字符來說都是微不足道的。

0

雖然每個調用級別確實擺脫自己的舊字符串,它創建保持自己的字符串。

(只是把這個在我自己的話閱讀其他的答案後,更直接地解決了我(作者問題)已經錯過了。)