データサイエンスや機械学習の現場において、扱うデータセットのサイズは年々肥大化を続けています。
2026年現在、数テラバイト規模の多次元配列を扱うことは珍しくありませんが、物理メモリ(RAM)の容量には限界があります。
PythonのNumPyライブラリで巨大な配列を扱おうとした際、MemoryErrorが発生して処理が止まってしまった経験を持つ方も多いのではないでしょうか。
このような課題を解決するための強力な機能が、ディスク上のファイルとメモリをマッピングする「numpy.memmap」です。
本記事では、メモリ不足を回避しながら巨大配列を効率的に操作するための実装テクニックを詳しく解説します。
NumPy memmapとは何か
numpy.memmapは、ディスク上に存在するバイナリファイルを、あたかもメモリ上の配列(ndarray)であるかのように扱うための機能です。
OSのメモリ管理システムを利用して、必要な時に必要な分だけデータをディスクから読み込む(遅延読み込み)仕組みを採用しています。
これにより、搭載されているRAMの容量を超える巨大なデータであっても、インデックス参照やスライシングなどのNumPy標準の操作が可能になります。
メモリマッピングのメリット
通常のNumPy配列は、データを全てメモリ上に展開する必要があります。
対して、memmapを利用すると、物理メモリの使用量を最小限に抑えつつ、高速なランダムアクセスを実現できます。
特に、データの一部しか使用しない場合や、複数のプロセスで同じデータを共有する場合に非常に高いパフォーマンスを発揮します。
基本的な実装方法
まずは、numpy.memmapを使用して巨大なファイルを作成し、そこに値を書き込む基本的な手順を見ていきましょう。
ここでは、ディスク上に「1GB」の配列を想定したファイルを作成する例を紹介します。
import numpy as np
import os
# ファイル名と配列の形状を定義
filename = "large_data.dat"
shape = (10000, 10000)
dtype = 'float64'
# 新規にmemmapオブジェクトを作成(書き込みモード 'w+')
# 実際にメモリが確保されるわけではなく、ディスク上にファイルが確保される
fp = np.memmap(filename, dtype=dtype, mode='w+', shape=shape)
# 配列の一部にデータを書き込む
fp[0, :] = np.random.rand(10000)
fp[1:5, 1:5] = 1.0
# 変更をディスクに反映(フラッシュ)
fp.flush()
# 作成されたファイルサイズを確認
file_size = os.path.getsize(filename)
print(f"File size: {file_size / (1024**3):.2f} GB")
File size: 0.75 GB
既存のファイルを読み込む
一度作成したmemmapファイルは、次回以降は読み込み専用モードなどで開くことができます。
これにより、巨大なデータを毎回ロードする時間を大幅に短縮できます。
# 既存のファイルを読み込み(読み取り専用モード 'r')
# 形状やデータ型を正しく指定する必要がある
new_fp = np.memmap(filename, dtype='float64', mode='r', shape=(10000, 10000))
# データの読み出し(メモリ効率が良い)
print(new_fp[0, :5])
[0.12345678 0.23456789 0.34567891 0.45678901 0.56789012]
実務で役立つ実装テクニック
単にmemmapを使うだけでなく、いくつかのテクニックを組み合わせることで、より堅牢で高速なシステムを構築できます。
1. 書き込みモードの使い分け
mode引数にはいくつかの種類があり、用途に応じて適切に選択することが重要です。
| モード | 意味 | 主な用途 |
|---|---|---|
r | 読み取り専用 | データの変更を行わない場合に使用します。 |
r+ | 読み書き可能(既存ファイル) | 既存のデータを更新する場合に使用します。 |
w+ | 読み書き可能(新規作成) | 新しく巨大なファイルを作成する場合に使用します。 |
copyonwrite | コピーオンライト | メモリ上で変更を加えても、ディスク上の元のファイルは変更されません。 |
2. メモリ不足を防ぐ「チャンク処理」
memmapを使用していても、配列全体に対して一度に計算を行うと、結果を保持するために新しいメモリが確保されてしまいます。
そのため、「スライスを用いたチャンクごとの処理」が推奨されます。
例えば、平均値を求める場合でも、全てのデータを一度にロードするのではなく、行ごとに計算を進めることでRAMの消費を一定以下に抑えることができます。
3. flushメソッドの活用
memmapへの書き込みは、即座にディスクへ書き込まれるわけではなく、OSのキャッシュに一時的に保持されることがあります。
プログラムがクラッシュした場合のデータ損失を防ぐため、重要な書き込みの後には必ずfp.flush()を呼び出すようにしましょう。
注意点と制約事項
numpy.memmapは万能ではありません。
いくつかの注意点を理解しておくことで、トラブルを未然に防ぐことができます。
OSのファイル制限
非常に多くのmemmapオブジェクトを同時に開くと、OSのファイル記述子(File Descriptor)の制限に達することがあります。
不要になったオブジェクトは、明示的に参照を削除(del fp)することをお勧めします。
ディスクI/Oのボトルネック
物理的なRAMに比べて、ディスクの読み書き速度は遥かに低速です。
そのため、ランダムアクセスが頻発するようなアルゴリズムでは、処理速度が著しく低下する可能性があります。
可能な限りデータが連続して並ぶようにアクセスする、あるいはSSDなどの高速なストレージを使用することがパフォーマンス向上の鍵となります。
まとめ
numpy.memmapは、Pythonで巨大な配列データを扱う際の救世主とも言える機能です。
ディスクリソースを効率的に活用することで、メモリ不足エラーを回避しつつ、大規模なデータ処理を可能にします。
最後に、今回のポイントを整理します。
numpy.memmapは、ディスク上のファイルをメモリ上の配列としてマッピングする。w+やr+などのモードを適切に選択し、書き込み後はflush()を推奨する。- 巨大な計算を行う際は、チャンク処理を取り入れてRAMの消費を抑える。
これらのテクニックを駆使して、最新の巨大データ解析に挑戦してみてください。
