Spec-Zone.ru › NumPy 1.18

Внутреннее устройство NumPy

  • Объяснения кода NumPy на C
    • Модель памяти
    • Инкапсуляция типов данных
    • Итераторы N-мерных массивов
    • Распространение
    • Массивно-скалярные значения
    • Индексирование
      • Расширенное индексирование
    • Универсальные функции
      • Настройка
      • Вызов функции
        • Один цикл
        • Цикл с шагом
        • Буферизованный цикл
      • Финальная обработка выходных данных
      • Методы
        • Настройка
        • Сведение
        • Накопление
        • Reduceat
  • Выравнивание памяти
    • Цели выравнивания NumPy
    • Переменные в NumPy, которые контролируют и описывают выравнивание
    • Последствия выравнивания

Внутренняя организация массивов NumPy

Понимание того, как массивы NumPy обрабатываются в скрытом виде, поможет лучше разобраться с NumPy. В этом разделе мы не будем вдаваться в подробности. Те, кто желают узнать все подробности, обратитесь к книге Трэвиса Олифанта «Руководство по NumPy».

Массивы NumPy состоят из двух основных компонентов: самих данных массива (далее буфер данных) и информации о данных. Буфер данных — это то, что обычно подразумевают под массивами в C или Fortran: непрерывный (и фиксированный) блок памяти, содержащий элементы фиксированного размера. NumPy также содержит набор данных, описывающих, как интерпретировать данные в буфере. Эта дополнительная информация содержит (среди прочего):

  1. Размер базового элемента данных в байтах
  2. Начальная позиция данных в буфере (смещение относительно начала буфера).
  3. Число измерений и размер каждого измерения
  4. Расстояние между элементами для каждого измерения («шаг»). Оно не обязательно должно быть кратным размеру элемента.
  5. Порядок байтов данных (который может отличаться от родного).
  6. Является ли буфер только для чтения
  7. Информация (через объект dtype) об интерпретации базового элемента данных. Базовый элемент может быть простым целым или вещественным числом, или составным объектом (например, подобным структуре), фиксированным полем символов или указателями на объекты Python.
  8. Порядок массива (C-порядок или Fortran-порядок).

Эта структура позволяет очень гибко использовать массивы. Она позволяет, например, просто менять метаданные, чтобы изменить интерпретацию буфера массива. Изменение порядка байтов массива — это простое изменение без переупорядочения данных. Форму массива можно изменить очень легко, не изменяя ничего в буфере данных и не копируя данные.

Среди прочего, это позволяет создать новый объект метаданных массива, использующий тот же буфер данных, чтобы создать новое представление этих данных с другой интерпретацией буфера (например, с другой формой, смещением, порядком байтов, шагами и т. д.), но с использованием одних и тех же байтов данных. Многие операции в NumPy делают именно это, например, срезы.

Обычно эти новые версии метаданных массива, но с тем же буфером данных, являются новыми «представлениями» буфера данных. Есть другой объект ndarray, но он использует тот же буфер данных. Вот почему необходимо принудительно создавать копии с помощью метода .copy(), если вы действительно хотите создать новую и независимую копию буфера данных.

Новые представления массивов означают, что счетчики ссылок объектов на буфер данных увеличиваются. Простое удаление исходного объекта массива не удалит буфер данных, если существуют другие представления.

Проблемы с порядком индексирования многомерных массивов

Какой правильный способ индексирования многомерных массивов? Прежде чем прийти к выводу о единственно верном способе индексирования многомерных массивов, стоит понять, почему это путаница. В этом разделе мы попытаемся подробно объяснить, как работает индексирование NumPy и почему мы используем принятую нами конвенцию для изображений и когда целесообразно использовать другие конвенции.

Первое, что нужно понять, — это две противоречивые конвенции индексирования двумерных массивов. Матричная нотация использует первый индекс для указания строки, а второй — для указания столбца. Это противоположно геометрически ориентированной конвенции для изображений, где люди, как правило, считают, что первый индекс представляет позицию x (то есть столбец), а второй — позицию y (то есть строку). Это само по себе является источником многих путаниц; пользователи, ориентированные на матрицы, и пользователи, ориентированные на изображения, ожидают разных вещей в отношении индексирования.

Вторая проблема — как индексы соответствуют порядку хранения массива в памяти. В Fortran первый индекс изменяется быстрее всего при перемещении по элементам двумерного массива, поскольку он хранится в памяти. Если вы примете матричную конвенцию индексирования, это означает, что матрица хранится по столбцам (поскольку при изменении первого индекса происходит переход к следующей строке). Таким образом, Fortran считается языком с столбцовой (column-major) структурой. У C противоположная конвенция. В C последний индекс изменяется быстрее всего, когда вы проходите по массиву, хранящемуся в памяти. Таким образом, C — язык с построчной (row-major) структурой. Матрица хранится построчно. Обратите внимание, что в обоих случаях предполагается использование матричной конвенции индексирования, то есть и для Fortran, и для C, первый индекс — это строка. Обратите внимание, что эта конвенция подразумевает, что конвенция индексирования неизменна, а порядок данных изменяется, чтобы это сохранить.

Но это не единственный способ взглянуть на это. Предположим, у вас есть большие двумерные массивы (изображения или матрицы), хранящиеся в файлах данных. Предположим, данные хранятся построчно, а не по столбцам. Если мы хотим сохранить нашу конвенцию индексирования (матричную или графическую), то в зависимости от используемого языка нам, возможно, придется переупорядочить данные при чтении в память, чтобы сохранить нашу конвенцию индексирования. Например, если мы читаем данные, упорядоченные по строкам, в память без переупорядочения, они будут соответствовать конвенции матричного индексирования для C, но не для Fortran. И наоборот, они будут соответствовать конвенции индексирования изображений для Fortran, но не для C. Для C, если вы используете данные, упорядоченные по строкам, и хотите сохранить конвенцию индексирования изображений, данные должны быть переупорядочены при чтении в память.

В конечном счете, то, что вы делаете для Fortran или C, зависит от того, что важнее: не переупорядочивать данные или сохранить конвенцию индексирования. Для больших изображений переупорядочение данных может быть дорогостоящим, и часто конвенция индексирования инвертируется, чтобы этого избежать.

Ситуация с NumPy делает эту проблему еще более сложной. Внутреннее устройство массивов NumPy достаточно гибкое, чтобы принимать любой порядок индексов. Можно просто переупорядочить индексы, манипулируя внутренней информацией о шагах массивов, не переупорядочивая данные вообще. NumPy будет знать, как сопоставить новый порядок индексов с данными без перемещения данных.

Итак, если это так, почему бы не выбрать порядок индексов, который наиболее соответствует вашим ожиданиям? В частности, почему бы не определить упорядоченные по строкам изображения, чтобы использовать графическую конвенцию? (Иногда это называется конвенцией Fortran против конвенции C, поэтому в NumPy есть опции «C» и «FORTRAN» для упорядочения массивов.) Недостатком этого является потенциальное ухудшение производительности. Часто данные обращаются последовательно, либо неявно в операциях с массивами, либо явно с помощью циклов по строкам изображения. Когда это делается, данные будут обращаться в неэффективном порядке. Когда увеличивается первый индекс, фактически происходит доступ к элементам, расположенным далеко друг от друга в памяти, что обычно приводит к плохим скоростям доступа к памяти. Например, для двумерного изображения «im», определённого так, что im[0, 10] представляет значение в точке x=0, y=10. Для согласованности с обычным поведением Python, im[0] представлял бы столбец в точке x=0. Однако эти данные были бы разбросаны по всему массиву, так как данные хранятся построчно. Несмотря на гибкость индексирования NumPy, оно не может скрыть тот факт, что базовые операции выполняются неэффективно из-за порядка данных или то, что получить непрерывные подмассивы по-прежнему неудобно (например, im[:,0] для первой строки, по сравнению с im[0]), поэтому нельзя использовать выражение, подобное для строки в im; для столбца в im, хотя оно и работает, но не возвращает непрерывные данные столбцов.

Оказывается, NumPy достаточно умен при работе с ufunc, чтобы определить, какой индекс изменяется быстрее всего в памяти, и использует его для внутреннего цикла. Таким образом, для ufunc не существует большого принципиального преимущества ни в одном из подходов в большинстве случаев. С другой стороны, использование .flat с массивом с порядком FORTRAN приведёт к неэффективному доступу к памяти, поскольку соседние элементы в сплющенном массиве (итераторе, по сути) не будут непрерывными в памяти.

END_OF_DOCUMENT_MARKER

Действительно, факт состоит в том, что индексация Python в списках и других последовательностях естественным образом приводит к порядку «снаружи внутрь» (первый индекс получает наибольшую группировку, следующий — следующую по величине, а последний — наименьший элемент). Поскольку данные изображения обычно хранятся по строкам, это соответствует положению внутри строк, являющемуся последним индексируемым элементом.

Если вы всё же хотите использовать порядок Fortran, знайте, что существует два подхода: 1) принять тот факт, что первый индекс не является самым быстро меняющимся в памяти, и заставить все ваши I/O-процедуры переупорядочивать данные при переходе из памяти на диск или наоборот, или использовать механизм numpy для сопоставления первого индекса с самыми быстро меняющимися данными. Мы рекомендуем первый подход, если это возможно. Недостаток второго заключается в том, что многие функции numpy вернут массивы без порядка Fortran, если вы не будете внимательны с использованием ключевого слова «order». Это было бы очень неудобно.

В противном случае мы рекомендуем просто научиться изменять обычный порядок индексов при обращении к элементам массива. Конечно, это противоречит интуиции, но это более соответствует семантике Python и естественному порядку данных.

© 2005–2020 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.18/reference/internals.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API