記憶體對齊#

NumPy 對齊目標#

在 NumPy 中,記憶體對齊有三個相關的使用案例(截至 1.14 版本)

  1. 建立結構化資料類型,其欄位的對齊方式如同 C 結構。

  2. 透過使用 uint 賦值取代 memcpy 來加速複製操作。

  3. 保證 ufuncs/setitem/casting 程式碼的安全對齊存取。

NumPy 使用兩種不同的對齊形式來達成這些目標:「真對齊」和「Uint 對齊」。

「真」對齊指的是 C 語言中等效 C 類型的架構相依對齊方式。例如,在 x64 系統中,float64 相當於 C 語言中的 double。在大多數系統上,這具有 4 或 8 位元組的對齊方式(這可以透過 GCC 選項 malign-double 控制)。如果變數的記憶體偏移量是其對齊方式的倍數,則該變數在記憶體中是對齊的。在某些系統(例如 sparc)上,記憶體對齊是必需的;在其他系統上,它可以提高速度。

「Uint」對齊取決於資料類型的大小。它被定義為 NumPy 的複製程式碼用於複製資料類型的 uint 的「真對齊」,如果沒有等效的 uint,則為未定義/未對齊。目前,NumPy 使用 uint8uint16uint32uint64uint64 分別複製大小為 1、2、4、8、16 位元組的資料,所有其他大小的資料類型都無法進行 uint 對齊。

例如,在(典型的 Linux x64 GCC)系統上,NumPy 的 complex64 資料類型實作為 struct { float real, imag; }。這具有 4 的「真」對齊和 8 的「uint」對齊(等於 uint64 的真對齊)。

uint 和真對齊不同的某些情況(預設 GCC Linux)

架構

類型

真對齊

uint 對齊

x86_64

complex64

4

8

x86_64

float128

16

8

x86

float96

4

-

NumPy 中控制和描述對齊的變數#

在 NumPy 中,align 這個詞有 4 個相關的用法

  • dtype.alignment 屬性(C 語言中的 descr->alignment)。這旨在反映類型的「真對齊」。它對於所有資料類型都具有架構相依的預設值,除了使用 align=True 建立的結構化類型,如下所述。

  • ndarray 的 ALIGNED 旗標,在 IsAligned 中計算,並由 PyArray_ISALIGNED 檢查。這是從 dtype.alignment 計算而來。如果陣列中的每個項目都位於與 dtype.alignment 一致的記憶體位置,則設定為 True,如果陣列的 data ptr 和所有 strides 都是該對齊方式的倍數,則會是這種情況。

  • dtype 建構函式的 align 關鍵字,僅影響結構化陣列。如果結構的欄位偏移量不是手動提供的,NumPy 會自動決定偏移量。在這種情況下,align=True 會填充結構,使每個欄位在記憶體中都是「真」對齊的,並將 dtype.alignment 設定為欄位「真」對齊中最大的值。這就像 C 結構通常所做的那樣。否則,如果手動提供了偏移量或 itemsize,align=True 只會檢查所有欄位是否都是「真」對齊的,以及總 itemsize 是否是最大欄位對齊方式的倍數。在任何一種情況下,dtype.isalignedstruct 也會設定為 True。

  • IsUintAligned 用於判斷 ndarray 是否為「uint 對齊」,方式與 IsAligned 檢查真對齊的方式類似。

對齊的後果#

以下是如何使用上述變數

  1. 建立對齊的結構:為了知道在 align=True 時如何偏移欄位,NumPy 會查找 field.dtype.alignment。這包括巢狀結構化陣列的欄位。

  2. Ufuncs:如果陣列的 ALIGNED 旗標為 False,ufuncs 將在評估之前緩衝/轉換陣列。這是必要的,因為 ufunc 內部迴圈直接存取原始元素,如果元素不是真對齊的,則在某些架構上可能會失敗。

  3. Getitem/setitem/copyswap 函數:與 ufuncs 類似,這些函數通常有兩個程式碼路徑。如果 ALIGNED 為 False,它們將使用緩衝引數的程式碼路徑,使其為真對齊。

  4. 跨步複製程式碼:此處改用「uint 對齊」。如果陣列的項目大小等於 1、2、4、8 或 16 位元組,且陣列為 uint 對齊,則 NumPy 將改為針對適當的 N 執行 *(uintN*)dst) = *(uintN*)src)。否則,NumPy 會透過執行 memcpy(dst, src, N) 來複製。

  5. Nditer 程式碼:由於這通常會呼叫跨步複製程式碼,因此必須檢查「uint 對齊」。

  6. 轉換程式碼:這會檢查「真」對齊,因為如果對齊,它會執行 *dst = CASTFUNC(*src)。否則,它會執行 memmove(srcval, src); dstval = CASTFUNC(srcval); memmove(dst, dstval),其中 dstval/srcval 是對齊的。

請注意,跨步複製和跨步轉換程式碼是深度交織的,因此它們處理的任何陣列都必須同時進行 uint 和真對齊,即使複製程式碼僅需要 uint 對齊,而轉換程式碼僅需要真對齊。如果將來要大幅重寫此程式碼,最好允許它們使用不同的對齊方式。