1

配列を操作する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。それにもかかわらず、何らかの方法で 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)

したがって、質問:データに十分なメモリがあるかどうかを最初に確認し、実際に配列全体のスペースを予約する方法は?

4

2 に答える 2

8

10 TB のファイルを生成しているという事実に「偽物」は何もありません

サイズの配列を求めています

2 ** 37 * 10 = 1374389534720 要素

の dtype は'i8'8 バイト (64 ビット) の整数を意味するため、最終的な配列のサイズは

1374389534720 * 8 = 10995116277760 バイト

また

10995116277760 / 1E12 = 10.99511627776 TB


空きディスク容量が 250 GB しかない場合、「10 TB」ファイルを作成するにはどうすればよいでしょうか?

かなり最新のファイルシステムを使用していると仮定すると、OS は、それらをバックアップするのに十分な物理ディスク容量が実際にあるかどうかに関係なく、ほぼ任意のサイズのスパース ファイルを生成できます。

たとえば、私の 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 GB 未満のデータをnp.memmap配列に書き込む必要がある場合、問題がない可能性があることを意味します (実際には、これはおそらく、配列内のどこに書き込みを行っているか、およびそれが行優先か列優先かにも依存します)。


プロセスが 10 TB の仮想メモリを使用するにはどうすればよいですか?

メモリ マップを作成すると、カーネルは呼び出し元プロセスの仮想アドレス空間内にアドレスの新しいブロックを割り当て、それらをディスク上のファイルにマップします。したがって、Python プロセスが使用している仮想メモリの量は、作成されたばかりのファイルのサイズだけ増加します。ファイルはスパースでもある可能性があるため、仮想メモリが使用可能な RAM の合計量を超えるだけでなく、マシンの物理ディスク領域の合計を超えることもあります。


np.memmapアレイ全体を格納するのに十分なディスク容量があるかどうかを確認するにはどうすればよいですか?

これを Python でプログラム的に行いたいと想定しています。

  1. 利用可能な空きディスク容量を取得します。この前の SO questionへの回答には、さまざまな方法が示されています。1 つのオプションは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
    
  2. 配列のサイズをバイト単位で計算します。

    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
    
  3. 2. > 1. かどうかを確認します。


更新:ファイルを割り当てる「安全な」方法はありnp.memmapますか?これにより、アレイ全体を格納するのに十分なディスク領域が予約されることが保証されますか?

可能性の 1 つ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

標準ライブラリを使用してこれを行うプラットフォームに依存しない方法については知りませんが、Posix ベースの OS で動作するfallocatePyPI の Python モジュールがあります。

于 2015-12-01T20:18:19.213 に答える
-1

@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には with オプション--sparse=neverが採用されています。

スクリプトが管理可能なサイズ パラメータ (たとえば 1 GB) で呼び出されると、配列は非スパース ファイルにマップされます。これは、コマンドの出力によって確認され、du -h現在は最大 1 GB のサイズが示されています。メモリが十分でない場合、スクリプトは次のエラーで終了します。

cp: ‘/tmp/tmps_thxud2’: write failed: No space left on device
于 2015-12-02T11:03:34.313 に答える