内存对齐#

NumPy 对齐目标#

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

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

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

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

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 属性(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是否“uint对齐”,其方式类似于IsAligned检查真对齐的方式。

对齐的结果#

以下是上述变量的使用方式

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

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

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

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