我有一个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

hdd的大小只有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)

因此,问题是:如何在开始时检查,是否有足够的内存存储数据,然后确实为整个数组保留空间?

最佳答案

你正在生成10 TB的文件这一事实没有什么“虚假的”
你要的是大小数组
2**37*10=1374389534720元件
dtype'i8'表示8字节(64位)整数,因此最终数组的大小为
1374389534720*8=1099511627760字节

1099511627760/1e12=10.99511627776 TB
如果只有250 GB的可用磁盘空间,那么如何创建“10 TB”文件?
假设您使用的是一个相当现代化的文件系统,那么您的操作系统将能够生成几乎任意大的sparse files,而不管您是否有足够的物理磁盘空间来支持它们。
例如,在我的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结束。这意味着,如果需要将np.memmap数组,则可能没有问题(实际上,这也可能取决于您在数组中的写入位置,以及它是row还是column major)。
进程如何可能使用10 TB的虚拟内存?
创建memory map时,内核会在调用进程的虚拟地址空间中分配一个新的地址块,并将它们映射到磁盘上的文件。因此,python进程使用的虚拟内存量将随着刚刚创建的文件的大小而增加。由于文件也可以是稀疏的,因此虚拟内存不仅可以超过可用的RAM总量,而且还可以超过计算机上的物理磁盘空间总量。
如何检查是否有足够的磁盘空间来存储完整的np.memmap阵列?
我假设您想用python以编程方式完成这项工作。
获取可用磁盘空间量。this previous SO question的答案中给出了各种方法。一种选择是:
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.>一。
更新:是否有一种“安全”的方法来分配一个os.statvfs文件,以确保保留足够的磁盘空间来存储完整的阵列?
一种可能是使用np.memmap预分配磁盘空间,例如:
~$ fallocate -l 1G bigfile

~$ du -h bigfile
1.1G    bigfile

您可以从python调用它,例如使用fallocate
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

我不知道使用标准库实现这一点的独立于平台的方法,但是对于任何基于posix的操作系统来说,都应该有一个subprocess.check_call的方法。

关于python - numpy.memmap:伪造的内存分配,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34023665/

10-13 01:48