内存对齐#
NumPy 的对齐目标#
NumPy 中与内存对齐相关的用例有三种(截至 1.14 版本)
NumPy 使用两种不同的对齐形式来实现这些目标:“真实对齐”和“Uint 对齐”。
“真实”对齐是指 C 中等效 C 类型的特定于体系结构(architecture-dependent)的对齐。例如,在 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)
arch
type
true-aln
uint-aln
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用于以类似于IsAligned检查真实对齐的方式来确定 ndarray 是否为“uint 对齐”。
对齐的后果#
上面这些变量的使用方式如下:
创建对齐的结构体:要知道在
align=True时如何偏移字段,NumPy 会查找field.dtype.alignment。这包括嵌套结构化数组的字段。Ufuncs:如果数组的
ALIGNED标志为 False,ufuncs 将在评估前缓冲/强制转换数组。这是必需的,因为 ufunc 的内部循环直接访问原始元素,如果元素未真实对齐,在某些体系结构上可能会失败。Getitem/setitem/copyswap 函数:与 ufuncs 类似,这些函数通常有两个代码路径。如果
ALIGNED为 False,它们将使用一个代码路径来缓冲参数,以便它们是真实对齐的。Strided copy code:在这里,使用“uint 对齐”代替。如果数组的 itemsize 等于 1、2、4、8 或 16 字节,并且数组是 uint 对齐的,那么 NumPy 将执行
*(uintN*)dst) = *(uintN*)src)(其中 N 是适当的值)。否则,NumPy 通过执行memcpy(dst, src, N)来复制。Nditer code:由于它经常调用 strided copy code,因此必须检查“uint 对齐”。
Cast code:这会检查“真实”对齐,因为它执行
*dst = CASTFUNC(*src)(如果已对齐)。否则,它会执行memmove(srcval, src); dstval = CASTFUNC(srcval); memmove(dst, dstval),其中 dstval/srcval 是已对齐的。
请注意,strided-copy 和 strided-cast 代码是紧密耦合的,因此处理它们的任何数组都必须同时进行 uint 和 true 对齐,尽管 copy-code 仅需要 uint 对齐,而 cast code 仅需要 true 对齐。如果将来对这段代码进行重大重写,允许它们使用不同的对齐方式将会很有益。