沒有什麼內存問題,是一行Python代碼解決不了的

內存不足是項目開發過程中經常碰到的問題,我和我的團隊在之前的一個項目中也遇到了這個問題,我們的項目需要存儲和處理一個相當大的動態列表,測試人員經常向我抱怨內存不足。但是最終,我們通過添加一行簡單的代碼解決了這個問題。

結果如圖所示:
沒有什麼內存問題,是一行Python代碼解決不了的
我將在下面解釋它的工作原理。
舉一個簡單的“learning”示例 - 創建一個DataItem類,在其中定義一些個人信息屬性,例如姓名,年齡和地址。沒有什麼內存問題,是一行Python代碼解決不了的
小測試——這樣一個對象會佔用多少內存?
首先讓我們嘗試下面這種測試方案:
d1 = DataItem("Alex", 42, "-")
print ("sys.getsizeof(d1):", sys.getsizeof(d1))
答案是56字節。看起來比較小,結果令人滿意。
但是,讓我們檢查另一個數據多一些的對象:
d2 = DataItem("Boris", 24, "In the middle of nowhere")
print ("sys.getsizeof(d2):", sys.getsizeof(d2))
答案仍然是56。這讓我們明白這個結果並不完全正確。
我們的直覺是對的,這個問題不是那麼簡單。Python是一種非常靈活的語言,具有動態類型,它在工作時存儲了許多額外的數據。這些額外的數據本身就佔了很多內存。
例如,sys.getsizeof(“ ”)返回33,沒錯,每個空行就多達33字節!並且sys.getsizeof(1)將爲此數字返回24-24個字節(我建議C程序員們現在點擊結束閱讀,以免對Python的美麗失去信心)。
對於更復雜的元素,例如字典,sys.getsizeof(dict())返回272個字節,這還只是一個空字典。舉例到此爲止,但事實已經很清楚了,何況RAM的製造商也需要出售他們的芯片。
現在,讓我們回到回到我們的DataItem類和“小測試”問題。
這個類到底佔多少內存?
首先,我們將以較低級別輸出該類的全部內容:
def dump(obj):
for attr in dir(obj):
print(" obj.%s = %r" % (attr, getattr(obj, attr)))
這個函數將顯示隱藏在“隱身衣”下的內容,以便所有Python函數(類型,繼承和其他包)都可以運行。
結果令人印象深刻:
沒有什麼內存問題,是一行Python代碼解決不了的
當前位置:技術分享 > 技術參考 > 正文
沒有什麼內存問題,是一行Python代碼解決不了的
2018-12-18 10:24:56 | 編輯:hely | 查看:96 | 評論:0
內存不足是項目開發過程中經常碰到的問題,我和我的團隊在之前的一個項目中也遇到了這個問題,我們的項目需要存儲和處理一個相當大的動態列表,測試人員經常向我抱怨內存不足。但是最終,我們通過添加一行簡單的代碼解決了這個問題。
內存不足是項目開發過程中經常碰到的問題,我和我的團隊在之前的一個項目中也遇到了這個問題,我們的項目需要存儲和處理一個相當大的動態列表,測試人員經常向我抱怨內存不足。但是最終,我們通過添加一行簡單的代碼解決了這個問題。
結果如圖所示:

我將在下面解釋它的工作原理。
舉一個簡單的“learning”示例 - 創建一個DataItem類,在其中定義一些個人信息屬性,例如姓名,年齡和地址。

小測試——這樣一個對象會佔用多少內存?
首先讓我們嘗試下面這種測試方案:
d1 = DataItem("Alex", 42, "-")
print ("sys.getsizeof(d1):", sys.getsizeof(d1))
答案是56字節。看起來比較小,結果令人滿意。
但是,讓我們檢查另一個數據多一些的對象:
d2 = DataItem("Boris", 24, "In the middle of nowhere")
print ("sys.getsizeof(d2):", sys.getsizeof(d2))
答案仍然是56。這讓我們明白這個結果並不完全正確。
我們的直覺是對的,這個問題不是那麼簡單。Python是一種非常靈活的語言,具有動態類型,它在工作時存儲了許多額外的數據。這些額外的數據本身就佔了很多內存。
例如,sys.getsizeof(“ ”)返回33,沒錯,每個空行就多達33字節!並且sys.getsizeof(1)將爲此數字返回24-24個字節(我建議C程序員們現在點擊結束閱讀,以免對Python的美麗失去信心)。
對於更復雜的元素,例如字典,sys.getsizeof(dict())返回272個字節,這還只是一個空字典。舉例到此爲止,但事實已經很清楚了,何況RAM的製造商也需要出售他們的芯片。
現在,讓我們回到回到我們的DataItem類和“小測試”問題。
這個類到底佔多少內存?
首先,我們將以較低級別輸出該類的全部內容:
def dump(obj):
for attr in dir(obj):
print(" obj.%s = %r" % (attr, getattr(obj, attr)))
這個函數將顯示隱藏在“隱身衣”下的內容,以便所有Python函數(類型,繼承和其他包)都可以運行。
結果令人印象深刻:
沒有什麼內存問題,是一行Python代碼解決不了的它總共佔用多少內存呢?
在GitHub上,有一個函數可以計算實際大小,通過遞歸調用所有對象的getsizeof實現。
沒有什麼內存問題,是一行Python代碼解決不了的
讓我們試一下:
沒有什麼內存問題,是一行Python代碼解決不了的
我們分別得到460和484字節,這似乎更接近事實。
使用這個函數,我們可以進行一系列實驗。例如,我想知道如果DataItem放在列表中,數據將佔用多少空間。
get_size([d1])函數返回532個字節,顯然,這些是“原本的”460+一些額外開銷。但是get_size([d1,d2])返回863個字節—小於460+484。get_size([d1,d2,d1])的結果更加有趣,它產生了871個字節,只是稍微多了一點,這說明Python很聰明,不會再爲同一個對象分配內存。
現在我們來看問題的第二部分。
是否有可能減少內存消耗?
答案是肯定的。Python是一個解釋器,我們可以隨時擴展我們的類,例如,添加一個新字段:
沒有什麼內存問題,是一行Python代碼解決不了的
這是一個很棒的特點,但是如果我們不需要這個功能,我們可以強制解釋器使用slots指令來指定類屬性列表:
沒有什麼內存問題,是一行Python代碼解決不了的
更多信息可以參考文檔中的“dictweakref的部分。使用dict所節省的空間可能會很大”。
我們嘗試後發現:get_size(d1)返回的是64字節,對比460直接,減少約7倍。作爲獎勵,對象的創建速度提高了約20%(請參閱文章的第一個屏幕截圖)。
真正使用如此大的內存增益不會導致其他開銷成本。只需添加元素即可創建100,000個數組,並查看內存消耗:
沒有什麼內存問題,是一行Python代碼解決不了的
在沒有slots的情況結果爲16.8MB,而使用slots時爲6.9MB。當然不是7倍,但考慮到代碼變化很小,它的表現依然出色。

現在討論一下這種方式的缺點。激活slots會禁止創建其他所有元素,包括dict,這意味着,例如,下面這種將結構轉換爲json的代碼將不起作用:
def toJSON(self):
return json.dumps(self.dict)
但這也很容易搞定,可以通過編程方式生成你的dict,遍歷循環中的所有元素:
沒有什麼內存問題,是一行Python代碼解決不了的
向類中動態添加新變量也是不可能的,但在我們的項目裏,這不是必需的。

下面是最後一個小測試。來看看整個程序需要多少內存。在程序末尾添加一個無限循環,使其持續運行,並查看Windows任務管理器中的內存消耗。
沒有slots
沒有什麼內存問題,是一行Python代碼解決不了的
69Mb變成27Mb......好吧,畢竟我們節省了內存。對於只添加一行代碼的結果來說已經很好了。
注意:tracemalloc調試庫使用了大量額外的內存。顯然,它爲每個創建的對象添加了額外的元素。如果你將其關閉,總內存消耗將會少得多,截圖顯示了2個選項:
沒有什麼內存問題,是一行Python代碼解決不了的
如何節省更多的內存?
可以使用numpy庫,它允許你以C風格創建結構,但在這個的項目中,它需要更深入地改進代碼,所以對我來說第一種方法就足夠了。
奇怪的是,slots的使用從未在Habré上詳細分析過,我希望這篇文章能夠填補這一空白。
結論
這篇文章看起來似乎是反Python的廣告,但它根本不是。Python是非常可靠的(爲了“刪除”Python中的程序,你必須非常努力),這是一種易於閱讀和方便編寫的語言。在許多情況下,這些優點遠勝過缺點,但如果你需要性能和效率的最大化,你可以使用numpy庫像C++一樣編寫代碼,它可以非常快速有效地處理數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章