前言
Python 的列表(list)是一個非常靈活的數組,可以隨意調整長度。正是因為這種便利,使得我們會情不自禁地去修改數組以滿足我們的需求,其中相比於 insert,pop等等而言,append用法更常見。
有像這樣使用:
也有像這樣使用的:
這樣用很開心,也很滿足。
但其實只要遇到能夠動態修改數據長度場景,我們都應該馬上反應過來一點,那就是內存管理的問題。
如果運行效率和便捷性同時滿足的話,那簡直就是大大的福音呀。
然而,上帝為你開啟一扇窗的同時肯定也已經關上了一扇門了!
吝嗇的初始化
深受預分配知識的薰陶,我們也是覺得 list 在初始化是有分配一定的長度的,要不然每次都申請內存那得多 ”low“ 啊。
然後實際上 list 真的就是這麼 ”low“:
我們的猜測是,list 在定義之後,會預先分配好一個一定大小的池用來塞數據,以避免動不動就申請內存。
但是在上面的實驗看出,一個成員的列表,比一個空列表,長度僅僅只是大了 8 字節,如果真的存在這樣一個預分配的池,那麼在預分配個數之內添加成員,兩者的內存大小應該是保持不變才對。
所以可以猜測這塊list應該是沒有這樣的一個預分配內存池。這裡需要來個實錘
當我們在執行test = [1]時,實際上只做了兩件事:
- 根據成員的數目,構建相應長度的空列表;(上述代碼)
- 一個個將這些成員塞進去;
可能有童鞋會覺得,在塞成員的那一步,說不定會觸發什麼機制使它變大?
很可惜,因為初始化用的方法是PyList_SET_ITEM, 所以這裡是木有的觸發什麼機制,只是簡單的數組成員賦值而已:
所以整個 list 的初始化,還真的就是木有預分配的內存池,直接按需申請,一個蘿蔔一個坑,實在得狠;
可變長的關鍵
初始化過程是這樣還可以理解,如果運行中還這樣的話,那就有點說不過去了。
試想下,在文章開頭用append的例子中,如果每append一個元素就申請一次內存,那麼list可能要被吐槽到懷疑人生了, 所以很明顯,在對於內存的申請,它還是有自己的套路的。
在list裡面,不管是insert、pop還是append,都會遇到list_resize,故名思義,這個函數就是用來調整 list 對象的內存佔用的。
在上面的代碼中,頻繁看到兩個名詞:newsize 和 new_allocated, 這裡需要解釋下,newsize 並不是增加/減少的個數,而是增加/減少之後的成員總數目。比方說:
上面的 append 觸發 list_resize 時,newsize 是 3 + 1, 而不是 1;這邊比較重要,因為在pop 這類減少列表成員時候,就是傳入縮減後的總數目。
在 list 的結構定義中,關於長度的定義有兩個,分別是 ob_size (實際的成員數),allocated (總成員數)
它們之間的關係就是:
所以 new_allocated 就很好理解了,這個就是新的總坑數。
當名詞含義理解得差不多時,我們就能順藤摸瓜知道一個列表在 list_resize 之後,大小會變成怎樣?
方法其實從上面註釋和代碼都說得很明白了,這裡再簡單整理下:
- 先確定一個基數:new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
- 判斷下 new_allocated + newsize 有沒有超過 PY_SIZE_MAX, 如果超過了,直接報錯;
- 最終確定新的總坑數是:new_allocated + newsize, 如果 newsize 是 0, 那麼總坑數直接為 0 ;
下面演示下
開始簡單的代入法一步步算:
其中:
- new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) + newsize (因為下面的 newsize > 0)
- 當原 allocated >= newsize 並且 newsize >= 原 allocated / 2 時,不改變 allocated 不申請內存直接返回
通過上面的表格,應該比較清楚看到什麼時候會觸發改變 allocated,並且當觸發時它們是如何計算的。為什麼我們需要這樣關注 allocated?理由很簡單,因為這個值決定了整個 list 的動態內存的佔用大小;
擴容是這樣,縮容也是照貓畫虎。反正都是算出新的 allocated, 然後由PyMem_RESIZE來處理。
總結
綜上所述,在一些明確列表成員或者簡單處理再塞入列表的情況下,我們不應該再用下面的方式:
而是應該用更加 pythonic 和 更加高效的列表推導式:test = [i for i in range(4)]。
經過作者授權轉載|原文鏈接:https://segmentfault.com/a/1190000017221754
Python零基礎和Docker+K8s課程優惠,需要的諮詢wechat:17812796384
閱讀更多 Python學習 的文章