- PVSM.RU - https://www.pvsm.ru -
Если ваш NumPy-массив слишком велик для того, чтобы полностью поместиться в оперативной памяти, его можно обработать, разбив на фрагменты [1]. Сделать это можно либо в прозрачном режиме, либо явно, загружая эти фрагменты с диска по одному.
В такой ситуации можно прибегнуть к двум классам инструментов:
memmap()
, прозрачный механизм, который позволяет воспринимать файл, расположенный на диске, так, будто он весь находится в памяти. У каждого из этих методов есть свои сильные и слабые стороны.
Материал, перевод которого мы сегодня публикуем, посвящён разбору особенностей этих методов работы с данными, и рассказу о том, в каких ситуациях они могут пригодиться. В частности, особое внимание будет уделено форматам данных, которые оптимизированы для выполнения вычислений и необязательно рассчитаны на передачу этих данных другим программистам.
Когда файл читают с диска в первый раз, операционная система не просто копирует данные в память процесса. Сначала она копирует эти данные в свою память, сохраняя их копию в так называемом «буферном кэше».
В чём здесь польза?
Дело в том, что операционная система хранит данные в кэше на тот случай, если нужно будет снова прочитать те же данные из того же файла.
Если данные читают снова — то они поступают в память программы уже не с диска, а из оперативной памяти, что на порядки быстрее.
Если память, занятая кэшем, нужна для чего-то другого, кэш будет автоматически очищен.
Когда данные пишут на диск, они перемещаются в обратном направлении. Сначала они пишутся лишь в буферный кэш. Это означает, что операции записи обычно выполняются весьма быстро, так как программе не нужно ориентироваться на медленный диск. Ей, в ходе записи, нужно работать лишь с оперативной памятью.
В итоге же данные сбрасываются на диск из кэша.
В нашем случае memmap()
позволяет воспринимать файл на диске так, как будто бы он представляет собой массив, хранящийся в памяти. Операционная система, прозрачно для программы, выполняет операции чтения/записи, обращаясь или к буферному кэшу, или к жёсткому диску, в зависимости от того, кэшированы ли запрашиваемые данные в памяти или нет. Здесь выполняется примерно такой алгоритм:
В качестве дополнительного плюса memmap()
можно отметить то, что в большинстве случаев буферный кэш для файла будет встроен в память программы. Это значит, что системе не придётся поддерживать дополнительную копию данных в памяти программы за пределами буфера.
Метод memmap()
встроен в NumPy:
import numpy as np
array = np.memmap("mydata/myarray.arr", mode="r",
dtype=np.int16, shape=(1024, 1024))
Выполните этот код, и в вашем распоряжении будет массив, работа с которым для программы будет совершенно прозрачной — независимо от того, осуществляется ли работа с буферным кэшем или с жёстким диском.
Хотя в определённых ситуациях memmap()
может весьма неплохо себя показывать, у этого метода есть и ограничения:
Поясним последний пункт. Представим, что у нас есть двумерный массив, содержащий 32-битные (4-х байтовые) целые числа. За одну операцию чтения с диска читается 4096 байт. Если с диска читают данные, расположенные в файле последовательно (скажем — такие данные находятся в строках массива), то после каждой операции чтения в нашем распоряжении окажется 1024 целых числа. Но если читают данные, расположение которых в файле не соответствует их расположению в массиве (скажем — данные, расположенные в столбцах), то каждая операция чтения позволит получить лишь 1 необходимое число. В результате окажется, что для получения того же объёма данных придётся выполнить в тысячу раз больше операций чтения.
Для того чтобы преодолеть вышеозначенные ограничения, можно воспользоваться форматами хранения данных Zarr [3] или HDF5, которые очень похожи:
Далее мы будем обсуждать только Zarr, но если вам интересен формат HDF5 и его более глубокое сравнение с Zarr — можете посмотреть это [6] видео.
Zarr позволяет хранить фрагменты данных и загружать их в память в виде массивов, а также — записывать эти фрагменты данных в виде массивов.
Вот как загрузить массив с использованием Zarr:
>>> import zarr, numpy as np
>>> z = zarr.open('example.zarr', mode='a',
... shape=(1024, 1024),
... chunks=(512,512), dtype=np.int16)
>>> type(z)
<class 'zarr.core.Array'>
>>> type(z[100:200])
<class 'numpy.ndarray'>
Обратите внимание на то, что до тех пор, пока не будет получен срез объекта, в нашем распоряжении не будет numpy.ndarray
. Сущность zarr.core.array
— это лишь метаданные. С диска загружаются только те данные, которые включены в срез.
memmap()
, рассмотренные выше:Остановимся на двух последних пунктах подробнее.
Предположим, мы работаем с массивом размером 30000x30000 элементов. Если нужно читать этот массив и перемещаясь по его оси X
, и перемещаясь по его оси Y
, сохранить фрагменты, содержащие данные этого массива, можно так, как показано ниже (на практике, скорей всего, понадобится больше 9 фрагментов):
Теперь данные, расположенные и по оси X
, и по оси Y
, можно загружать эффективно. В зависимости от того, какие именно данные нужны в программе, можно загрузить, например, фрагменты (1, 0), (1, 1), (1, 2), или фрагменты (0, 1), (1, 1), (2, 1).
Каждый фрагмент можно сжать. Это означает, что данные могут поступать в программу быстрее, чем диск позволяет читать несжатую информацию. Если данные сжаты в 3 раза, это значит, что их можно загружать с диска в 3 раза быстрее, чем несжатые данные, за вычетом времени, необходимого процессору на их распаковку.
После того, как фрагменты загружены, их можно убрать из памяти программы.
Чем лучше пользоваться — memmap()
или Zarr?
Memmap()
интересно выглядит в таких случаях:
memmap()
, смогут совместно использовать один и тот же буферный кэш. Это означает, что в памяти нужно держать лишь одну копию данных, независимо от того, сколько выполняется процессов.Zarr особенно полезен в следующих ситуациях (в некоторых из них, как будет отмечено, применим и формат HDF5):
Я бы, выбирая между memmap()
и Zarr, в первую очередь попробовал бы воспользоваться Zarr — из-за той гибкости, которую даёт этот пакет и реализуемый им формат хранения данных.
Уважаемые читатели! Как вы решаете задачу работы с большими NumPy-массивами?
Автор: ru_vds
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka/348677
Ссылки в тексте:
[1] фрагменты: https://pythonspeed.com/articles/data-doesnt-fit-in-memory/#chunks
[2] Image: https://habr.com/ru/company/ruvds/blog/490630/
[3] Zarr: https://zarr.readthedocs.io/en/stable/
[4] pytables: https://www.pytables.org/
[5] h5py: https://www.h5py.org/
[6] это: https://www.youtube.com/watch?v=-l445lCPTts
[7] Источник: https://habr.com/ru/post/490630/?utm_source=habrahabr&utm_medium=rss&utm_campaign=490630
Нажмите здесь для печати.