内存对齐#

NumPy 对齐目标#

在NumPy中,与内存对齐相关的使用情况有三种(截至1.14版本):

  1. 使用与C语言结构体中对齐的 字段 创建 结构化数据类型.

  2. 通过使用 uint 赋值而不是 memcpy 来加速复制操作.

  3. 保证 ufuncs/setitem/casting 代码的安全对齐访问.

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

“True” 对齐指的是与C语言中相应C类型的架构依赖对齐.例如,在x64系统中 float64 等同于C中的 double.在大多数系统上,这具有4或8字节的对齐(这可以通过GCC的选项 malign-double 来控制).如果变量的内存偏移是其对齐的倍数,则该变量在内存中对齐.在某些系统(例如sparc)上,内存对齐是必需的;在其他系统上,它提供了加速.

“Uint” 对齐取决于数据类型的大小.它被定义为 NumPy 复制代码用于复制数据类型的”真实对齐”,或者如果没有等效的 uint,则为未定义/未对齐.目前,NumPy 使用 uint8uint16uint32uint64uint64 分别复制大小为 1、2、4、8、16 字节的数据,所有其他大小的数据类型不能进行 uint 对齐.

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

一些情况下 uint 和 true 对齐方式不同(默认 GCC Linux):

arch

类型

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 和数组的所有步长都是该对齐的倍数.

  • dtype 构造函数的 align 关键字,仅影响 结构化数组.如果未手动提供结构的字段偏移量,NumPy 会自动确定偏移量.在这种情况下,``align=True`` 会填充结构,使每个字段在内存中”真实”对齐,并将 dtype.alignment 设置为字段”真实”对齐的最大值.这类似于 C 结构通常的做法.否则,如果手动提供了偏移量或 itemsize,``align=True`` 仅检查所有字段是否”真实”对齐,并且总 itemsize 是最大字段对齐的倍数.无论哪种情况,:attr:dtype.isalignedstruct 也会设置为 True.

  • IsUintAligned 用于确定一个 ndarray 是否是”uint 对齐”的,类似于 IsAligned 检查真实对齐的方式.

对齐的后果#

以下是如何使用上述变量:

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

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

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

  4. 跨步复制代码:这里,使用了”uint alignment”.如果数组的itemsize等于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 和 true 对齐的,尽管复制代码只需要 uint 对齐,转换代码只需要 true 对齐.如果这段代码将来被重写,最好允许它们使用不同的对齐方式.