在 NumPy 中,數組(ndarray)之所以能夠高效執行向量化計算,其核心原因并不僅在于“多維結構”,而在于其底層內存模型(memory model)的設計。
理解數組的內存組織方式,是理解以下問題的關鍵:
? 為什么數組有“連續”之分
? 為什么切片通常不復制數據
? 為什么 reshape 可以在 O(1) 時間完成
? 為什么某些操作必須產生副本
本篇將從底層機制出發,系統介紹 NumPy 的數組內存模型及 strides 的工作原理。
一、數組的本質結構
一個 NumPy 數組可以抽象為三部分:
(1)數據緩沖區(data buffer)
(2)數組結構信息(metadata:shape/dtype/ndim 等)
(3)數據解釋規則(strides 配合 dtype)
其中:
? data buffer:一塊連續的內存區域,存儲實際數據
? shape:數組在各維度上的長度
? dtype:元素類型(決定每個元素占用的字節數)
? strides:決定如何從 data buffer 中取數據
示例:二維數組
[10, 11, 12]])其基本屬性:
print(a.strides) # (24, 8) (可能因平臺不同略有差異)二、數組數據在內存中的布局
在 NumPy 中,多維數組在內存中的存儲并不是“真正的多維結構”,而是一維連續內存的線性排列。所謂“二維”“三維”,本質上只是通過 shape、strides 與 dtype 對一維數據進行解釋的結果。
NumPy 同時支持兩種內存布局方式:
? C-order(行優先,row-major)
? F-order(列優先,column-major)
以便與不同語言庫進行高效交互,避免數據重排帶來的性能損失。
![]()
1、C-order(行優先)
按“行”進行存儲:先存儲第 0 行的所有元素,再存儲第 1 行,以此類推。
“C” 的意思是按 C/C++ 語言中的數組數據排列方式。
上例數組 a 在內存中的排列為:
[ 1 2 3 4 5 6 7 8 9 10 11 12]在 NumPy 中,默認采用 C-order(行優先)存儲方式。
2、F-order(列優先)
按“列”進行存儲:先存儲第 0 列的所有元素,再存儲第 1 列,依此類推。
“F” 的意思是按 Fortran/MATLAB 中的數組數據排列方式。
同一數組 a 在 F-order 下的排列為:
[ 1 4 7 10 2 5 8 11 3 6 9 12]3、指定內存布局
在創建或變換數組時,可以指定存儲方式。
示例 1:
# (24, 8)示例 2:轉換為 F-order
# (8, 32)4、數組存放的連續性
NumPy 的底層數據緩沖區通常是連續分配的。不過,一般所說的數組的“連續性”(contiguous)是針對當前 ndarray 的 shape 與 strides 而言的。
某些切片或步長操作雖然不會改變底層原始數據的存放方式,但新得到的視圖數組可能已經不再滿足連續數組的條件。
也就是說,“連續性”描述的是數組在當前解釋方式下,元素在內存中是否按線性順序排列。
可使用數組屬性flags來判斷:
a.flags常見字段有:
? C_CONTIGUOUS(行連續)
? F_CONTIGUOUS(列連續)
示例:
print(a.flags['C_CONTIGUOUS']) # True有些視圖體現了非連續性。
示例:
# F_CONTIGUOUS : False可以看出,底層數據的連續性并沒有改變,而只是改變了底層數據的訪問方式,這一點體現在數組屬性 strides 上。
三、什么是 strides
數組元素strides是一個元組,表示在每個維度上移動一個單位,需要跨越的字節數。
strides 決定了“如何從一維內存中解釋出多維數組”。
比如,對于數組 a:
[8, 9, 10, 11]])假設 dtype 為 int64,即每個元素在內存中占 8 字節,那么 a.strides 則為 (32, 8)。
![]()
其含義是:
? 沿第 0 維(行)移動 → 跨越 32 字節(4 × 8)
? 沿第 1 維(列)移動 → 跨越 8 字節
可以將 strides 理解為“步長規則”,告訴解釋器:下一元素在哪里。
四、strides 與常見操作的關系
1、索引的本質:地址計算
當訪問:
a[i, j]本質計算:
地址 = base_address + i × strides[0] + j × strides[1]這正是 NumPy 能夠不復制數據、快速構造視圖的根本原因。
2、視圖的本質
視圖(view)是一個新的數組對象,它與原數組共享底層數據緩沖區,但數組結構可能不同。
可以更精確地表述為:
視圖 = 相同 data buffer + 不同 strides / shape示例 1:切片
print(b.strides) # (24, 8)此時沒有發生數據復制:
? data buffer:相同
? shape:變為 (2, 2)
? strides:保持或調整
示例 2:步長切片
b = a[:, ::2]結果:
[4 6]]本質上是通過修改 strides(列步長 ×2)實現,數據仍來自原數組。
3、reshape 的本質
示例:
b = a.reshape(3, 2)reshape 的核心不是“重排數據”,而是重新解釋 strides + shape,因此可在 O(1) 時間完成,通常不復制數據。
需要注意的是,當數組內存布局無法滿足新的形狀要求時,reshape() 也可能返回副本:
print(np.shares_memory(b, c)) # False由于 b 是通過步長切片得到的非連續數組,其內存布局無法直接被 reshape 解釋,因此 reshape() 在此情況下會返回副本。
也就是說,reshape 是否返回視圖,取決于原數組的內存布局是否能夠被重新解釋。
4、轉置(transpose)
使用數組屬性 T 可對數組進行轉置:
b = a.T轉置操作不會復制數據,而是交換 shape 和交換 strides。
5、廣播(broadcast)
廣播本質上是通過構造特殊 strides(為 0)來復用數據。這意味著在某些維度上移動時,地址保持不變,從而實現數據復用。
示例:
b = np.broadcast_to(a, (2,3))輸出:
[1 2 3]]原數組被廣播為形狀 (2,3) 的二維數組。多行共享同一數據。
小結
NumPy 數組本質上是“數據緩沖區 + 數組結構信息 + 數據解釋規則”的組合,其中 strides 決定了如何從連續內存中解析出多維結構。視圖、切片、reshape 等操作,本質上都是通過修改 shape 與 strides 來實現的,而非復制數據。理解這一機制,是深入掌握 NumPy 內存模型與性能優化的關鍵。
“點贊有美意,贊賞是鼓勵”
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.