numpy.memmap: фиктивное выделение памяти
У меня есть python3
скрипт, который работает с numpy.memmap
массивы. Он записывает массив во вновь созданный временный файл, который находится в /tmp
:
import numpy, tempfile
size = 2 ** 37 * 10
tmp = tempfile.NamedTemporaryFile('w+')
array = numpy.memmap(tmp.name, dtype = 'i8', mode = 'w+', shape = size)
array[0] = 666
array[size-1] = 777
del array
array2 = numpy.memmap(tmp.name, dtype = 'i8', mode = 'r+', shape = size)
print('File: {}. Array size: {}. First cell value: {}. Last cell value: {}'.\
format(tmp.name, len(array2), array2[0], array2[size-1]))
while True:
pass
Размер жесткого диска составляет всего 250G. Тем не менее, он может каким-то образом генерировать большие файлы 10T в /tmp
и соответствующий массив все еще кажется доступным. Вывод скрипта следующий:
File: /tmp/tmptjfwy8nr. Array size: 1374389534720. First cell value: 666. Last cell value: 777
Файл действительно существует и отображается размером 10T:
$ ls -l /tmp/tmptjfwy8nr
-rw------- 1 user user 10995116277760 Dec 1 15:50 /tmp/tmptjfwy8nr
Тем не менее, весь размер /tmp
гораздо меньше:
$ df -h /tmp
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 235G 5.3G 218G 3% /
Процесс также претендует на использование виртуальной памяти 10T, что также невозможно. Выход из top
команда:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
31622 user 20 0 10.000t 16592 4600 R 100.0 0.0 0:45.63 python3
Насколько я понимаю, это означает, что во время вызова numpy.memmap
необходимая память для всего массива не выделяется, и поэтому отображаемый размер файла является поддельным. Это, в свою очередь, означает, что когда я начну постепенно заполнять весь массив своими данными, в какой-то момент моя программа потерпит крах или мои данные будут повреждены.
Действительно, если я введу следующее в своем коде:
for i in range(size):
array[i] = i
Через некоторое время я получаю сообщение об ошибке:
Bus error (core dumped)
Поэтому вопрос: как проверить в начале, действительно ли памяти достаточно для данных, а затем действительно зарезервировать пространство для всего массива?
2 ответа
Нет ничего "поддельного" в том, что вы генерируете файлы размером 10 ТБ
Вы просите массивы размера
2 ** 37 * 10 = 1374389534720 элементов
Тип D 'i8'
означает 8-байтовое (64-битное) целое число, поэтому ваш конечный массив будет иметь размер
1374389534720 * 8 = 10995116277760 байт
или же
10995116277760 / 1E12 = 10.99511627776 ТБ
Если у вас есть только 250 ГБ свободного дискового пространства, как вы можете создать файл "10 ТБ"?
Предполагая, что вы используете достаточно современную файловую систему, ваша ОС будет способна генерировать практически произвольно большие разреженные файлы, независимо от того, достаточно ли у вас физического места на диске для их резервного копирования.
Например, на моей машине с Linux мне разрешено делать что-то вроде этого:
# I only have about 50GB of free space...
~$ df -h /
Filesystem Type Size Used Avail Use% Mounted on
/dev/sdb1 ext4 459G 383G 53G 88% /
~$ dd if=/dev/zero of=sparsefile bs=1 count=0 seek=10T
0+0 records in
0+0 records out
0 bytes (0 B) copied, 0.000236933 s, 0.0 kB/s
# ...but I can still generate a sparse file that reports its size as 10 TB
~$ ls -lah sparsefile
-rw-rw-r-- 1 alistair alistair 10T Dec 1 21:17 sparsefile
# however, this file uses zero bytes of "actual" disk space
~$ du -h sparsefile
0 sparsefile
Попробуйте позвонить du -h
на ваше np.memmap
файл после того, как он был инициализирован, чтобы увидеть, сколько фактического дискового пространства он использует.
Когда вы начнете фактически записывать данные в ваш np.memmap
файл, все будет в порядке, пока вы не превысите физическую емкость вашего хранилища, после чего процесс завершится с Bus error
, Это означает, что если вам нужно записать < 250 ГБ данных в ваш np.memmap
с массивом, то проблем может не быть (на практике это, вероятно, также будет зависеть от того, где вы пишете в массиве, и от того, является ли он основным или столбцом строки).
Как процесс может использовать 10 ТБ виртуальной памяти?
Когда вы создаете карту памяти, ядро выделяет новый блок адресов в виртуальном адресном пространстве вызывающего процесса и отображает их в файл на вашем диске. Поэтому объем виртуальной памяти, который использует ваш процесс Python, будет увеличиваться на размер только что созданного файла. Поскольку файл также может быть разреженным, виртуальная память может не только превышать общий объем доступной оперативной памяти, но также может превышать общий объем физического дискового пространства на вашей машине.
Как вы можете проверить, достаточно ли у вас места на диске для хранения полного np.memmap
массив?
Я предполагаю, что вы хотите сделать это программно на Python.
Получите объем свободного места на диске. В ответах на этот предыдущий вопрос SO приведены различные методы. Один вариант
os.statvfs
:import os def get_free_bytes(path='/'): st = os.statvfs(path) return st.f_bavail * st.f_bsize print(get_free_bytes()) # 56224485376
Определите размер вашего массива в байтах:
import numpy as np def check_asize_bytes(shape, dtype): return np.prod(shape) * np.dtype(dtype).itemsize print(check_asize_bytes((2 ** 37 * 10,), 'i8')) # 10995116277760
Проверьте, 2. > 1.
Обновление: есть ли "безопасный" способ выделить np.memmap
файл, который гарантирует, что на диске достаточно места для хранения всего массива?
Одной из возможностей может быть использование fallocate
предварительно выделить место на диске, например:
~$ fallocate -l 1G bigfile
~$ du -h bigfile
1.1G bigfile
Вы можете вызвать это из Python, например, используя subprocess.check_call
:
import subprocess
def fallocate(fname, length):
return subprocess.check_call(['fallocate', '-l', str(length), fname])
def safe_memmap_alloc(fname, dtype, shape, *args, **kwargs):
nbytes = np.prod(shape) * np.dtype(dtype).itemsize
fallocate(fname, nbytes)
return np.memmap(fname, dtype, *args, shape=shape, **kwargs)
mmap = safe_memmap_alloc('test.mmap', np.int64, (1024, 1024))
print(mmap.nbytes / 1E6)
# 8.388608
print(subprocess.check_output(['du', '-h', 'test.mmap']))
# 8.0M test.mmap
Мне неизвестен независимый от платформы способ сделать это с помощью стандартной библиотеки, но есть fallocate
Модуль Python на PyPI, который должен работать для любой ОС на основе Posix.
Основываясь на ответе @ali_m, я наконец пришел к такому решению:
# must be called with the argumant marking array size in GB
import sys, numpy, tempfile, subprocess
size = (2 ** 27) * int(sys.argv[1])
tmp_primary = tempfile.NamedTemporaryFile('w+')
array = numpy.memmap(tmp_primary.name, dtype = 'i8', mode = 'w+', shape = size)
tmp = tempfile.NamedTemporaryFile('w+')
check = subprocess.Popen(['cp', '--sparse=never', tmp_primary.name, tmp.name])
stdout, stderr = check.communicate()
if stderr:
sys.stderr.write(stderr.decode('utf-8'))
sys.exit(1)
del array
tmp_primary.close()
array = numpy.memmap(tmp.name, dtype = 'i8', mode = 'r+', shape = size)
array[0] = 666
array[size-1] = 777
print('File: {}. Array size: {}. First cell value: {}. Last cell value: {}'.\
format(tmp.name, len(array), array[0], array[size-1]))
while True:
pass
Идея состоит в том, чтобы скопировать изначально сгенерированный разреженный файл в новый нормальный. За это cp
с возможностью --sparse=never
Используется.
Когда скрипт вызывается с параметром управляемого размера (скажем, 1 ГБ), массив сопоставляется с не разреженным файлом. Это подтверждается выводом du -h
команда, которая теперь показывает размер ~1 ГБ. Если памяти недостаточно, сценарии завершаются с ошибкой:
cp: ‘/tmp/tmps_thxud2’: write failed: No space left on device