内存对齐#
NumPy 对齐目标#
在 NumPy 中 (截至 1.14) 有三个与内存对齐相关的用例。
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
属性 (descr->alignment
在 C 中)。它旨在反映类型的“真实对齐方式”。对于所有数据类型,它都有体系结构相关的默认值,除了使用align=True
创建的结构化类型外,如下所述。ndarray 的
ALIGNED
标志,在IsAligned
中计算,并由PyArray_ISALIGNED
检查。它是根据dtype.alignment
计算的。如果数组中每个项目的内存位置都与dtype.alignment
一致,则它被设置为True
,即如果数组的data ptr
和所有步长都是该对齐方式的倍数。dtype 构造函数的
align
关键字,它只影响 结构化数组。如果结构体的字段偏移量没有手动提供,NumPy 会自动确定偏移量。在这种情况下,align=True
会对结构体进行填充,以便每个字段在内存中“真实”对齐,并将dtype.alignment
设置为所有字段“真实”对齐方式中的最大值。这类似于 C 结构体通常的做法。否则,如果手动提供了偏移量或项目尺寸,align=True
只会检查所有字段是否“真实”对齐,以及总项目尺寸是否为最大字段对齐方式的倍数。在任一情况下,dtype.isalignedstruct
也被设置为 True。IsUintAligned
用于确定 ndarray 是否“uint 对齐”,其方式类似于IsAligned
如何检查真实对齐方式。
对齐方式的后果#
以下是上述变量的用法。
创建对齐的结构体:为了知道在
align=True
时如何偏移字段,NumPy 会查找field.dtype.alignment
。这包括嵌套结构化数组的字段。Ufuncs:如果数组的
ALIGNED
标志为 False,ufunc 会在计算之前缓冲/转换数组。这是因为 ufunc 内部循环直接访问原始元素,如果元素没有真实对齐,则在某些体系结构上可能会失败。Getitem/setitem/copyswap 函数:与 ufunc 类似,这些函数通常有两个代码路径。如果
ALIGNED
为 False,它们会使用一个代码路径,该路径会缓冲参数,使其真实对齐。步长复制代码:这里使用“uint 对齐方式”。如果数组的项目尺寸等于 1、2、4、8 或 16 字节,并且数组是 uint 对齐的,那么 NumPy 会执行
*(uintN*)dst) = *(uintN*)src)
(对于适当的 N)。否则,NumPy 会通过执行memcpy(dst, src, N)
来复制数据。Nditer 代码:由于它经常调用步长复制代码,因此它必须检查“uint 对齐方式”。
转换代码:它检查“真实”对齐方式,因为它在对齐时会执行
*dst = CASTFUNC(*src)
。否则,它会执行memmove(srcval, src); dstval = CASTFUNC(srcval); memmove(dst, dstval)
,其中 dstval/srcval 是对齐的。
请注意,步长复制代码和步长转换代码紧密相连,因此任何由它们处理的数组都必须是 uint 对齐和真实对齐,即使复制代码只需要 uint 对齐方式,转换代码只需要真实对齐方式。如果以后要对这段代码进行大幅重写,最好允许它们使用不同的对齐方式。