内存对齐#

NumPy 对齐目标#

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

  1. 创建 结构化数据类型,其 字段 对齐方式与 C 结构体中的对齐方式相同。

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

  3. 为 ufunc/setitem/转换代码保证安全的对齐访问。

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,“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 如何检查真实对齐方式。

对齐方式的后果#

以下是上述变量的用法。

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

  2. Ufuncs:如果数组的 ALIGNED 标志为 False,ufunc 会在计算之前缓冲/转换数组。这是因为 ufunc 内部循环直接访问原始元素,如果元素没有真实对齐,则在某些体系结构上可能会失败。

  3. Getitem/setitem/copyswap 函数:与 ufunc 类似,这些函数通常有两个代码路径。如果 ALIGNED 为 False,它们会使用一个代码路径,该路径会缓冲参数,使其真实对齐。

  4. 步长复制代码:这里使用“uint 对齐方式”。如果数组的项目尺寸等于 1、2、4、8 或 16 字节,并且数组是 uint 对齐的,那么 NumPy 会执行 *(uintN*)dst) = *(uintN*)src) (对于适当的 N)。否则,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 对齐方式,转换代码只需要真实对齐方式。如果以后要对这段代码进行大幅重写,最好允许它们使用不同的对齐方式。