Spec-Zone.ru › NumPy 1.16

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

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

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

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

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

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

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

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

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

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

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

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

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

Вторая проблема заключается в том, как индексы соответствуют порядку хранения массива в памяти. В Fortran первый индекс изменяется быстрее всего при перемещении по элементам двумерного массива, поскольку он хранится в памяти. Если принять матричное соглашение для индексирования, это означает, что матрица хранится по столбцам (поскольку первый индекс переходит к следующей строке при изменении). Поэтому Fortran считается языком со столбцовым порядком. В C используется обратное соглашение. В C последний индекс изменяется быстрее всего при перемещении по массиву в памяти. Таким образом, C — язык со строчным порядком. Матрица хранится по строкам. Обратите внимание, что в обоих случаях предполагается, что используется матричное соглашение для индексирования, т. е. и для 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]), поэтому нельзя использовать конструкции вроде for row in im; for col in im, хотя они работают, но не дают непрерывных данных столбцов.

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

END_OF_DOCUMENT_MARKER ```

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

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

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

© 2005–2019 NumPy Developers
Licensed under the 3-clause BSD License.
https://docs.scipy.org/doc/numpy-1.16.1/reference/internals.html

Spec-Zone.ru

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