在Python中,列表是一個動態的指針數組,而array模塊所提供的array對象則是保存相同類型的數值的動態數組。因爲array直接保存值,所以它所使用的內存比列表少。列表和array都是動態數組,所以往其中添加新元素,而沒有空間保存新的元素時,它們會自動從新分配內存塊,並將原來的內存中的值複製到新的內存塊中。爲了減小從新分配內存的次數,一般每次從新分配時,大小都爲原來的k倍。k值越大,則從新分配內存的次數越少,但浪費的空間越多。本節經過一系列的實驗觀察列表和array的內存分配模式。html
list聲明後結構大致分爲3部分,變量名稱--list對象(結構性數據+指針數組)--list內容,其中id表示的是list對象的位置,python
v引用變量名稱,v[:]引用list對象,此規則對python其餘序列結構也成立,如下示範可用id佐證,數組
a=b時,a和b指向同一個list對象app
a=b[:]時,a的list對象和b的list對象指向同一個list內容函數
除此以外[0]和[:1]也是不一樣的:post
In [30]: a[0] Out[30]: 1 In [31]: a[:1] Out[31]: [1]
空list佔用空間測試
In [32]: sys.getsizeof([]) Out[32]: 64
sys.getsizeof()能夠得到列表所佔用的內存大小。請編寫程序計算一個長度爲10000的列表,它的每一個下標都保存列表增加到此下標時的大小:優化
import sys # 【你的程序】計算size列表,它的每一個小標都保存增加到此下標時size列表的大小 size = [] for i in range(10000): size.append(sys.getsizeof(size)) import pylab as pl pl.plot(size, lw=2, c='b') pl.show()
圖中每一個階梯跳變的位置都表示一次內存分配,而每一個階梯的長度表示內存分配多出來的大小。 url
請編寫程序計算表示每次分配內存時列表的內存大小的resize_pos數組:spa
import numpy as np #【你的程序】計算resize_pos,它的每一個元素是size中每次分配內存的位置 # 可使用NumPy的diff()、where()、nonzero()快速完成此計算。 size = [] for i in range(10000): size.append(sys.getsizeof(size)) size = np.array(size) new_size = np.diff(size) resize_pos = size[np.where(new_size)] # resize_pos = size[np.nonzero(new_size)] pl.plot(resize_pos, lw=2) pl.show() print ("list increase rate:") tmp = resize_pos[25:].astype(np.float) # ❶ print (np.average(tmp[1:]/tmp[:-1])) # ❷
由圖可知曲線呈指數增加,第45次分配內存時,列表的大小已經接近10000。
❷爲了計算增加率,只須要計算resize_pos數組先後兩個值的商的平均值便可。
❶爲了提升精度,咱們只計算後半部分的平均值,注意須要用astype()方法將整數數組轉換爲浮點數數組。程序的輸出以下:
list increase rate:
1.12754776209
【注】np.where索引定位的兩種用法,np.nonzero非零值bool判斷的用法,np.diff差分函數的用法。
咱們能夠用scipy.optimize.curve_fit()對resize_pos數組進行擬合,擬合函數爲指數函數:
請編寫程序用上面的公式對resize_pos數組進行擬合:
from scipy.optimize import curve_fit #【你的程序】用指數函數對resize_pos數組進行擬合 def func(x, a, b, c, d): return a * np.exp(b * x + c) + d xdata = range(len(resize_pos)) ydata = resize_pos popt, pcov = curve_fit(func, xdata, ydata) y = [func(i, *popt) for i in xdata] pl.plot(xdata, y, lw=1, c='r') pl.plot(xdata, ydata, lw=1, c='b') pl.show() print ("list increase rate by curve_fit:") print (10**popt[1])
list increase rate by curve_fit: 1.31158606108
【注意】本程序中對於scipy中的指數擬合作了示範。
首先見得的測試一下list對象存儲的內容(結構3)的內存地址,
In [1]: a=[1,2,3,'a','b','c','de',[4,5]] In [2]: id(a) Out[2]: 139717112576840 In [3]: for i in a: ...: print(id(i)) ...: 139717238769920 139717238769952 139717238769984 139717239834192 139717240077480 139717240523888 139717195281104 139717112078024 In [4]: for i in a[6]: ...: print(id(i)) ...: 139717240220952 139717240202048 In [5]: for i in a[7]: ...: print(id(i)) ...: 139717238770016 139717238770048
而後看一下相對地址,
In [6]: for i in a: ...: print(id(i)-139717238769920) ...: 0 32 64 1064272 1307560 1753968 -43488816 -126691896 In [7]: for i in a[6]: ...: print(id(i)-139717238769920) ...: 1451032 1432128 In [8]: for i in a[7]: ...: print(id(i)-139717238769920) ...: 96 128
可見,對於list對象,其元素內容並不必定線性存儲,可是因爲內存分配的問題,會出現線性存儲的假象,當元素出現容器或者相對前一個元素類型改變時,內存空間就會再也不連續。
其實Q1已經回答了這個問題,畢竟元素地址自己就不連續,不過咱們仍是測試了一下,
In [22]: id(a[0])-id(a) Out[22]: 126193080
相差甚遠,並且咱們分析源碼可知,list對象主體是一個指針數組,也就是id(a)所指的位置主體是一個指向元素位置的指針數組,固然還有輔助的對象頭信息之類的(python中幾個常見的「黑盒子」之 列表list)。
In [16]: sys.getsizeof([1,2,3,'a','b','c','de']) Out[16]: 120 In [17]: sys.getsizeof([1,2,3,'a','b','c']) Out[17]: 112 In [18]: sys.getsizeof([1,2,3,'a','b']) Out[18]: 104
可見,list每個對象佔用8字節32位空間,咱們來看切片,
In [20]: sys.getsizeof(a[:3]) Out[20]: 88 In [21]: sys.getsizeof(a[:4]) Out[21]: 96 In [23]: sys.getsizeof(a[3:4]) Out[23]: 72 In [24]: sys.getsizeof(a[3:5]) Out[24]: 80
切片對象也是每一個元素佔8字節,可是切片也是list對象,即便從中間切(不切頭),也會包含頭信息的存儲佔用。
遺憾的是,不管array對象的長度是多少,sys.getsizeof()的結果都不變。所以沒法用上節的方法計算array對象的增加因子。
因爲內存分配時會耗費比較長的時間,所以能夠經過測量每次增長元素的時間,找到內存分配時的長度。請編寫測量增長元素的時間的程序:
from array import array import time #【你的程序】計算往array中添加元素的時間times times = [] times_step = [] arrays = [array('l') for i in range(1000)] start = time.time() for i in range(1000): start_step = time.time() [a.append(i) for a in arrays] end = time.time() times_step.append(end-start_step) times.append(end-start) pl.figure() pl.plot(times) pl.figure() pl.plot(times_step) pl.show()
輸出兩幅圖,前面的表示元素個數對應的程序總耗時,後面的表示每一次添加元素這一過程的耗時,注意,這張圖只有在array數量較大時纔是這個形狀,數組數量不夠時折線圖差別很大。
進一步的,咱們分析一下耗時顯著大於附近點(極大值)的時刻的序列對應此時元素數量的折線圖。
ts = np.array(times_step) le = range(np.sum(ts>0.00025)) si = np.squeeze(np.where(ts>0.00025)) pl.plot(le,si,lw=2) pl.show()
以MXNet中數組爲例,講解一下序列計算時的內存變化以及優化方式,