numpy.memmap: 伪造的内存分配
numpy.memmap: bogus memory allocation
我有一个使用 numpy.memmap
数组的 python3
脚本。它将数组写入位于 /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。尽管如此,它能以某种方式生成/tmp
中的10T大文件,并且相应的数组似乎仍然可以访问。脚本的输出如下:
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)
因此,问题:如何在开始时检查,是否真的有足够的内存用于数据然后确实为整个数组保留space ?
关于您正在生成 10 TB 文件这一事实没有任何意义'bogus'
您要求的数组大小为
2 ** 37 * 10 = 1374389534720 elements
'i8'
的 dtype 表示一个 8 字节(64 位)整数,因此您的最终数组的大小为
1374389534720 * 8 = 10995116277760 bytes
或
10995116277760 / 1E12 = 10.99511627776 TB
如果您只有 250 GB 的可用磁盘 space 那么您如何创建“10 TB”的文件?
假设您使用的是相当现代的文件系统,您的 OS 将能够生成几乎任意大的文件 sparse files,无论您实际上是否有足够的物理磁盘 space 支持他们。
例如,在我的 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
尝试在 np.memmap
文件初始化后调用 du -h
以查看它实际使用了多少磁盘 space。
当您实际开始将数据写入 np.memmap
文件时,一切都会正常,直到您超过存储的物理容量,此时进程将终止并显示 Bus error
。这意味着如果您需要向 np.memmap
数组写入 < 250GB 的数据,那么可能没有问题(实际上这可能还取决于您在数组中写入的位置,以及它是行还是专栏专业)。
一个进程怎么可能使用 10 TB 的虚拟内存?
当您创建 memory map 时,内核会在调用进程的虚拟地址 space 中分配一个新的地址块,并将它们映射到磁盘上的一个文件。因此,您的 Python 进程使用的虚拟内存量将随着刚刚创建的文件的大小而增加。由于文件也可以是稀疏的,因此虚拟内存不仅可以超过可用 RAM 的总量,还可以超过计算机上的总物理磁盘 space。
如何检查是否有足够的磁盘 space 来存储完整的 np.memmap
阵列?
我假设您想在 Python 中以编程方式执行此操作。
获取可用的可用磁盘量 space。 this previous SO question. One option is 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.
更新:有没有'safe'分配np.memmap
文件的方法,保证有足够的磁盘space预留来存储完整数组?
一种可能是使用 fallocate
预分配磁盘 space,例如:
~$ 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 module on PyPI 应该适用于任何基于 Posix 的 OS .
根据@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 GB)调用脚本时,数组将映射到非稀疏文件。 du -h
命令的输出证实了这一点,现在显示 ~1 GB 大小。如果内存不足,脚本将退出并出现错误:
cp: ‘/tmp/tmps_thxud2’: write failed: No space left on device
我有一个使用 numpy.memmap
数组的 python3
脚本。它将数组写入位于 /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。尽管如此,它能以某种方式生成/tmp
中的10T大文件,并且相应的数组似乎仍然可以访问。脚本的输出如下:
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)
因此,问题:如何在开始时检查,是否真的有足够的内存用于数据然后确实为整个数组保留space ?
关于您正在生成 10 TB 文件这一事实没有任何意义'bogus'
您要求的数组大小为
2 ** 37 * 10 = 1374389534720 elements
'i8'
的 dtype 表示一个 8 字节(64 位)整数,因此您的最终数组的大小为
1374389534720 * 8 = 10995116277760 bytes
或
10995116277760 / 1E12 = 10.99511627776 TB
如果您只有 250 GB 的可用磁盘 space 那么您如何创建“10 TB”的文件?
假设您使用的是相当现代的文件系统,您的 OS 将能够生成几乎任意大的文件 sparse files,无论您实际上是否有足够的物理磁盘 space 支持他们。
例如,在我的 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
尝试在 np.memmap
文件初始化后调用 du -h
以查看它实际使用了多少磁盘 space。
当您实际开始将数据写入 np.memmap
文件时,一切都会正常,直到您超过存储的物理容量,此时进程将终止并显示 Bus error
。这意味着如果您需要向 np.memmap
数组写入 < 250GB 的数据,那么可能没有问题(实际上这可能还取决于您在数组中写入的位置,以及它是行还是专栏专业)。
一个进程怎么可能使用 10 TB 的虚拟内存?
当您创建 memory map 时,内核会在调用进程的虚拟地址 space 中分配一个新的地址块,并将它们映射到磁盘上的一个文件。因此,您的 Python 进程使用的虚拟内存量将随着刚刚创建的文件的大小而增加。由于文件也可以是稀疏的,因此虚拟内存不仅可以超过可用 RAM 的总量,还可以超过计算机上的总物理磁盘 space。
如何检查是否有足够的磁盘 space 来存储完整的 np.memmap
阵列?
我假设您想在 Python 中以编程方式执行此操作。
获取可用的可用磁盘量 space。 this previous SO question. One option is
的答案中给出了多种方法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.
更新:有没有'safe'分配np.memmap
文件的方法,保证有足够的磁盘space预留来存储完整数组?
一种可能是使用 fallocate
预分配磁盘 space,例如:
~$ 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 module on PyPI 应该适用于任何基于 Posix 的 OS .
根据@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 GB)调用脚本时,数组将映射到非稀疏文件。 du -h
命令的输出证实了这一点,现在显示 ~1 GB 大小。如果内存不足,脚本将退出并出现错误:
cp: ‘/tmp/tmps_thxud2’: write failed: No space left on device