Выравнивание памяти#

Цели выравнивания NumPy#

Существует три случая использования, связанных с выравниванием памяти в NumPy (по состоянию на 1.14):

  1. Создание структурированные типы данных с поля выровнены как в C-структуре.

  2. Ускорение операций копирования с использованием uint присваивание вместо memcpy.

  3. Гарантирование безопасного выровненного доступа для ufuncs/setitem/casting кода.

NumPy использует две различные формы выравнивания для достижения этих целей: «Истинное выравнивание» и «Выравнивание Uint».

"Истинное" выравнивание относится к архитектурно-зависимому выравниванию эквивалентного C-типа в C. Например, в системах x64 float64 эквивалентно double в C. В большинстве систем это имеет выравнивание 4 или 8 байт (и это можно контролировать в GCC с помощью опции malign-double). Переменная выровнена в памяти, если её смещение в памяти кратно её выравниванию. На некоторых системах (например, sparc) выравнивание памяти обязательно; на других оно даёт ускорение.

Выравнивание "Uint" зависит от размера типа данных. Оно определяется как "истинное выравнивание" uint, используемого кодом копирования NumPy для копирования типа данных, или неопределённое/невыровненное, если нет эквивалентного 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):

архитектура

тип

true-aln

uint-aln

x86_64

complex64

4

8

x86_64

float128

16

8

x86

float96

4

-

Переменные в NumPy, которые контролируют и описывают выравнивание#

Существует 4 соответствующих использования слова align используется в NumPy:

  • The dtype.alignment атрибут (descr->alignment в C). Это предназначено для отражения «истинного выравнивания» типа. Имеет зависящие от архитектуры значения по умолчанию для всех типов данных, кроме структурированных типов, созданных с помощью align=True как описано ниже.

  • The ALIGNED флаг ndarray, вычисленный в IsAligned и проверяется с помощью PyArray_ISALIGNED. Это вычисляется из dtype.alignment. Установлено в True если каждый элемент в массиве находится в ячейке памяти, соответствующей dtype.alignment, что имеет место, если data ptr и все шаги массива кратны этому выравниванию.

  • The align ключевое слово конструктора dtype, которое влияет только на Структурированные массивы. Если смещения полей структуры не указаны вручную, NumPy определяет смещения автоматически. В этом случае align=True дополняет структуру так, чтобы каждое поле было «истинно» выровнено в памяти и устанавливает dtype.alignment быть наибольшим из "истинных" выравниваний полей. Это похоже на то, что обычно делают C-структуры. В противном случае, если смещения или размер элемента были предоставлены вручную align=True просто проверяет, что все поля выровнены «истинно» и что общий размер элементов кратен наибольшему выравниванию поля. В любом случае dtype.isalignedstruct также установлено в True.

  • IsUintAligned используется для определения, является ли ndarray "uint выровненным", аналогично тому, как IsAligned проверяет на истинное выравнивание.

Последствия выравнивания#

Вот как используются переменные выше:

  1. Создание выровненных структур: Чтобы узнать, как сместить поле, когда align=True, NumPy ищет field.dtype.alignment. Это включает поля, которые являются вложенными структурированными массивами.

  2. Универсальные функции: Если ALIGNED флаг массива равен False, ufuncs будут буферизовать/приводить массив перед вычислением. Это необходимо, поскольку внутренние циклы ufunc обращаются к сырым элементам напрямую, что может не сработать на некоторых архитектурах, если элементы не выровнены по истине.

  3. Функция getitem/setitem/copyswap: аналогично ufuncs, эти функции обычно имеют два пути выполнения. Если ALIGNED равно False, они будут использовать путь кода, который буферизует аргументы, чтобы они были истинно выровнены.

  4. Код копирования с шагом: здесь вместо этого используется «uint alignment». Если размер элемента массива равен 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, а код приведения — только истинного выравнивания. Если когда-нибудь будет большая переработка этого кода, было бы хорошо позволить им использовать разные выравнивания.