内存对齐#

NumPy 对齐目标#

在 NumPy 中(截至 1.14 版本),有三个与内存对齐相关的用例:

  1. 创建 structured datatypes ,其 fields 的对齐方式与 C 结构体类似.

  2. 通过使用 uint 赋值代替 memcpy 来加速复制操作.

  3. 保证 ufuncs/setitem/强制转换代码的安全对齐访问.

NumPy 使用两种不同的对齐方式来实现这些目标:"真对齐"和"Uint 对齐".

"真"对齐是指等效 C 类型在 C 语言中的架构相关对齐.例如,在 x64 系统中, float64 等效于 C 语言中的 double .在大多数系统上,这具有 4 或 8 字节的对齐方式(这可以通过 GCC 选项 malign-double 来控制).如果一个变量的内存偏移量是其对齐方式的倍数,则该变量在内存中是对齐的.在某些系统(例如 sparc)上,需要内存对齐;在其他系统上,它可以提高速度.

"Uint"对齐取决于数据类型的大小.它被定义为 NumPy 的复制代码用于复制数据类型的 uint 的"真"对齐方式,如果不存在等效的 uint,则为未定义/未对齐.目前,NumPy 使用 uint8 , uint16 , uint32 , uint64uint64 分别复制大小为 1,2,4,8,16 字节的数据,所有其他大小的数据类型都不能 uint 对齐.

例如,在(典型的 Linux x64 GCC)系统上,NumPy 的 complex64 数据类型被实现为 struct { float real, imag; } .这具有 4 的"真"对齐和 8 的"uint"对齐(等于 uint64 的真对齐).

uint 对齐和真对齐不同的示例(默认 GCC Linux):

arch

type

true-aln

uint-aln

x86_64

complex64

4

8

x86_64

float128

16

8

x86

float96

4

-

NumPy 中控制和描述对齐的变量#

NumPy 中使用了 4 个与 align 相关的术语:

  • dtype.alignment 属性(C 语言中的 descr->alignment ).这旨在反映类型的"真对齐".对于所有数据类型,它都具有依赖于架构的默认值,但使用 align=True 创建的结构化类型除外,如下所述.

  • ndarray 的 ALIGNED 标志,在 IsAligned 中计算,并由 PyArray_ISALIGNED 检查.这是从 dtype.alignment 计算得出的.如果数组中的每个项目都位于与 dtype.alignment 一致的内存位置,则将其设置为 True ,如果数组的 data ptr 和所有步幅都是该对齐方式的倍数,则就是这种情况.

  • dtype 构造函数的 align 关键字,它仅影响 结构化数组 .如果结构的字段偏移量不是手动提供的,则 NumPy 会自动确定偏移量.在这种情况下, align=True 会填充结构,以便每个字段在内存中都"真"对齐,并将 dtype.alignment 设置为字段"真"对齐方式中的最大值.这就像 C 结构通常所做的那样.否则,如果手动提供了偏移量或 itemsize,则 align=True 仅检查所有字段是否已"真"对齐,并且总 itemsize 是否是最大字段对齐方式的倍数.在任何一种情况下, dtype.isalignedstruct 也会设置为 True.

  • IsUintAligned 用于确定 ndarray 是否以类似于 IsAligned 检查真对齐的方式进行"uint 对齐".

对齐的后果#

以下是上述变量的使用方式:

  1. 创建对齐的结构:为了知道在 align=True 时如何偏移字段,NumPy 会查找 field.dtype.alignment .这包括嵌套的结构化数组的字段.

  2. Ufuncs:如果数组的 ALIGNED 标志为 False,则 ufuncs 将在评估之前缓冲/强制转换该数组.这是必需的,因为 ufunc 内部循环直接访问原始元素,如果元素未真对齐,则某些架构上可能会失败.

  3. Getitem/setitem/copyswap 函数:与 ufuncs 类似,这些函数通常具有两个代码路径.如果 ALIGNED 为 False,它们将使用缓冲参数的代码路径,以便它们是真对齐的.

  4. 跨步复制代码:这里使用"uint 对齐"代替.如果数组的 itemsize 等于 1,2,4,8 或 16 字节,并且数组是 uint 对齐的,那么 NumPy 将执行 *(uintN)dst) = (uintN)src) 以获得合适的 N.否则,NumPy 通过执行 memcpy(dst, src, N) 进行复制.

  5. Nditer 代码:由于这经常调用跨步复制代码,因此它必须检查"uint 对齐".

  6. Cast 代码:这会检查"true"对齐,因为如果对齐,它会执行 *dst = CASTFUNC(src) . 否则,它会执行 memmove(srcval, src); dstval = CASTFUNC(srcval); memmove(dst, dstval) ,其中 dstval/srcval 是对齐的.

请注意,跨步复制和跨步转换代码是紧密相连的,因此它们处理的任何数组都必须是 uint 对齐和 true 对齐的,即使复制代码只需要 uint 对齐,而转换代码只需要 true 对齐. 如果将来要大幅重写此代码,最好允许它们使用不同的对齐方式.