内存对齐#
NumPy 对齐目标#
在 NumPy 中(截至 1.14),有三个与内存对齐相关的用例:
创建 structured datatypes ,其 fields 像 C 结构体一样对齐.
通过使用
uint赋值代替memcpy来加速复制操作.保证 ufuncs/setitem/casting 代码的安全对齐访问.
NumPy 使用两种不同的对齐方式来实现这些目标:“真对齐”和“Uint 对齐”.
“真”对齐是指等效 C 类型在 C 中的体系结构相关的对齐方式.例如,在 x64 系统中, float64 等效于 C 中的 double .在大多数系统中,这具有 4 或 8 字节的对齐方式(这可以通过 GCC 选项 malign-double 来控制).如果变量的内存偏移量是其对齐方式的倍数,则该变量在内存中是对齐的.在某些系统(例如 sparc)上,需要内存对齐;在其他系统上,它可以提高速度.
“Uint”对齐取决于数据类型的大小.它被定义为 NumPy 的复制代码用于复制数据类型的 uint 的“真对齐”,或者如果不存在等效的 uint,则为未定义/未对齐.目前,NumPy 使用 uint8 , uint16 , uint32 , uint64 和 uint64 分别复制大小为 1,2,4,8,16 字节的数据,所有其他大小的数据类型都不能进行 uint 对齐.
例如,在(典型的 Linux x64 GCC)系统上,NumPy complex64 数据类型实现为 struct { float real, imag; } .它的“真”对齐为 4,而“uint”对齐为 8(等于 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和数组的所有步幅都是该对齐方式的倍数,则就是这种情况.dtype 构造函数的
align关键字,仅影响 结构化数组 .如果结构的字段偏移量不是手动提供的,则 NumPy 会自动确定偏移量.在这种情况下,align=True会填充结构,以便每个字段在内存中都是“真”对齐的,并将dtype.alignment设置为字段“真”对齐方式的最大值.这就像 C 结构通常所做的那样.否则,如果手动提供了偏移量或 itemsize,则align=True仅检查所有字段是否为“真”对齐,并且总 itemsize 是否为最大字段对齐方式的倍数.在任何一种情况下,dtype.isalignedstruct也会设置为 True.IsUintAligned用于确定 ndarray 是否以类似于IsAligned检查真对齐的方式“uint 对齐”.
对齐的后果#
以下是以上变量的使用方式:
创建对齐的结构体:为了知道当
align=True时如何偏移一个字段,NumPy 会查找field.dtype.alignment.这包括嵌套的结构化数组字段.Ufuncs:如果数组的
ALIGNED标志为 False,ufuncs 将在评估之前缓冲/转换该数组.这是必需的,因为 ufunc 内部循环直接访问原始元素,如果元素不是真正对齐的,则可能在某些架构上失败.Getitem/setitem/copyswap 函数:与 ufuncs 类似,这些函数通常有两条代码路径.如果
ALIGNED为 False,它们将使用一条缓冲参数以便它们是真正对齐的代码路径.步长复制代码:这里,使用“uint 对齐”代替.如果数组的 itemsize 等于 1,2,4,8 或 16 字节,并且数组是 uint 对齐的,那么 NumPy 将会执行
*(uintN)dst) = (uintN)src),其中 N 是适当的.否则,NumPy 通过执行memcpy(dst, src, N)来复制.Nditer 代码:由于这通常会调用步长复制代码,因此它必须检查“uint 对齐”.
Cast 代码:这会检查“真正”对齐,因为它在对齐时会执行
*dst = CASTFUNC(src).否则,它会执行memmove(srcval, src); dstval = CASTFUNC(srcval); memmove(dst, dstval),其中 dstval/srcval 是对齐的.
请注意,步长复制和步长转换代码是紧密相关的,因此由它们处理的任何数组都必须是 uint 和真正对齐的,即使复制代码只需要 uint 对齐,而转换代码只需要真正对齐.如果将来对这段代码进行重大重写,最好允许它们使用不同的对齐方式.