はじめに
Windows で RAM ディスクを使いたいとき、真っ先に候補に挙がるのは ImDisk Toolkit や OSFMount といったツールです。しかしこれらには共通の制約があります——管理者権限が必要、あるいは有償ライセンスが必要です。CI 環境や共有の開発マシンでは使えないことも多い。
でも、少し視点を変えると別のアプローチが見えてきます。
Python プロセスのメモリそのものが、Python コードにとっての RAM ディスクではないか。
この記事では、ドライバも管理者権限も不要な「Python 専用ソフトウェア RAM ディスク」として D-MemFS を活用する方法を紹介します。
まず結論:手法の比較
「どの方法を選べばいいのか」を最初にまとめます。
| 比較項目 | SSD上の tempfile
|
RAMディスク(ImDisk等) | D-MemFS |
|---|---|---|---|
| 管理者権限 | 不要 | 必要 | 不要 |
| 外部ツール | 不要 | 必要 | 不要(pip のみ) |
| 揮発性(自動消去) | △ 手動削除 | △ 再起動で消える | ✅ GC で自動回収 |
| クロスプラットフォーム | ✅ | ❌ Windows のみ | ✅ Win/Mac/Linux |
| 他プロセスからのアクセス | ✅ | ✅ | ❌ Python内限定 |
| メモリ上限管理 | ❌ | ❌ | ✅ ハードクォータ + メモリーガード |
| シーケンシャル I/O | ~1.9 GB/s | ~2.0 GB/s | ~1.9 GB/s |
| ランダムアクセス I/O | ~1.4 GB/s | ~1.3 GB/s | ~1.4 GB/s |
| CI環境での利用 | ✅ | ❌ 権限の壁 | ✅ |
結論: 外部コマンドを呼ぶ必要がない純粋な Python 処理なら、D-MemFS が最も手軽で高速な選択肢です。
💡 v0.3.0 新機能:メモリーガード
ハードクォータが「仮想 FS 内の予算管理」なのに対し、v0.3.0 で追加されたメモリーガードはホストマシンの物理メモリ残量をチェックし、確保できない場合に書き込みを事前に拒否します。クォータを 4 GiB に設定しても空きメモリが 2 GiB しかなければ意味がない——メモリーガードはその問題を防ぎます。詳しくは[第3弾:ハードクォータの設計思想]で解説します。
前提:なぜ RAM ディスクが欲しいのか
RAM ディスクの主な用途は次の 2 つです。
- 高速な一時ファイル処理 — ディスク I/O のレイテンシを排除して処理を速くしたい
- ディスクへの書き込みを避けたい — SSD の書き込み寿命を守りたい、センシティブなデータを残したくない
いずれも Python のテストや CI、データ処理パイプラインで頻繁に出てくる要件です。
D-MemFS:Python 専用のソフトウェア RAM ディスク
D-MemFS (dmemfs) は純粋 Python のインメモリファイルシステムライブラリです。
pip install D-MemFS
- 標準ライブラリのみ(ゼロ外部依存)
- 管理者権限不要
- Windows / macOS / Linux で動作
- プロセス終了時に自動消去(揮発性)
tempfile を D-MemFS に置き換える
最も典型的なパターンは、tempfile.TemporaryDirectory() の置き換えです。
置き換え前(ディスクへの書き込みが発生する)
import tempfile
import os
def process_data(raw: bytes) -> bytes:
with tempfile.TemporaryDirectory() as tmpdir:
input_path = os.path.join(tmpdir, "input.bin")
output_path = os.path.join(tmpdir, "output.bin")
with open(input_path, "wb") as f:
f.write(raw)
# 何らかの処理(外部コマンドを呼ぶ想定の例)
data = open(input_path, "rb").read()
result = bytes(b ^ 0xFF for b in data) # ダミー変換
with open(output_path, "wb") as f:
f.write(result)
return open(output_path, "rb").read()
置き換え後(すべてメモリ上で完結)
from dmemfs import MemoryFileSystem
def process_data(raw: bytes) -> bytes:
mfs = MemoryFileSystem()
mfs.mkdir("/tmp")
with mfs.open("/tmp/input.bin", "wb") as f:
f.write(raw)
with mfs.open("/tmp/input.bin", "rb") as f:
data = f.read()
result = bytes(b ^ 0xFF for b in data)
with mfs.open("/tmp/output.bin", "wb") as f:
f.write(result)
with mfs.open("/tmp/output.bin", "rb") as f:
return f.read()
ディスクへのアクセスがゼロになります。mfs がスコープを外れると GC に回収されるので、後始末も不要です。
GC による自動解放
MemoryFileSystem はスコープを抜けると GC に回収されます。TemporaryDirectory に近い感覚で使えます。
from dmemfs import MemoryFileSystem
def process():
mfs = MemoryFileSystem(max_quota=64 * 1024 * 1024)
mfs.mkdir("/work")
with mfs.open("/work/data.csv", "wb") as f:
f.write(b"id,value\n1,100\n2,200\n")
with mfs.open("/work/data.csv", "rb") as f:
print(f.read().decode())
process()
# 関数を抜けると mfs は GC に回収され、内容はすべて消える
ベンチマーク
以下は D-MemFS、RAM ディスク上の tempfile、SSD 上の tempfile を比較したベンチマークの結果です(repeat=5, warmup=1)。
計測環境: Windows、X:\TEMP(RAM ディスク)/ C:\TempX(SSD)
スループット値は 512 MiB の write + read から算出。
小ファイルの大量 read/write(300 ファイル × 4 KiB)
| 方法 | 時間 (ms) |
|---|---|
io.BytesIO |
6 |
| D-MemFS | 51 |
| tempfile(RAM ディスク上) | 207 |
| tempfile(SSD 上) | 267 |
ストリーム read/write(16 MiB、64 KiB チャンク)
| 方法 | 時間 (ms) |
|---|---|
| tempfile(RAM ディスク上) | 20 |
| tempfile(SSD 上) | 21 |
io.BytesIO |
62 |
| D-MemFS | 81 |
ランダムアクセス(16 MiB)
| 方法 | 時間 (ms) |
|---|---|
| D-MemFS | 34 |
| tempfile(SSD 上) | 35 |
| tempfile(RAM ディスク上) | 37 |
io.BytesIO |
82 |
大容量ストリーム read/write(512 MiB、1 MiB チャンク)
| 方法 | 時間 (ms) |
|---|---|
| D-MemFS | 529 |
| tempfile(RAM ディスク上) | 514 |
| tempfile(SSD 上) | 541 |
io.BytesIO |
2 258 |
多数ファイルのランダム読み込み(10,000 ファイル × 4 KiB)
| 方法 | 時間 (ms) |
|---|---|
| D-MemFS | 1 280 |
| tempfile(RAM ディスク上) | 6 310 |
| tempfile(SSD 上) | 8 601 |
ポイント:
- 小ファイル・多数ファイルで D-MemFS が圧倒的に速い。ファイルの open/close オーバーヘッドがほぼゼロなため、300 ファイルで 4 倍、10,000 ファイルのランダム読み込みで 5 倍以上の差がつく。
- 大容量ストリーム(512 MiB 以上、大きなチャンク)では D-MemFS と tempfile は同等。メモリ帯域がボトルネックになるため、格納先の違いが出にくい。
- 16 MiB 程度の小〜中容量ストリームは tempfile の方が速い。D-MemFS は 1 チャンクあたりのオーバーヘッドが相対的に大きく、チャンクが細かいほど差が開く。
-
io.BytesIOは単一ストリーム用途では最速だが、ファイル管理機能(パス・ディレクトリ・クォータ)を持たない。
非同期コードでの使用(AsyncMemoryFileSystem)
asyncio ベースのコードには AsyncMemoryFileSystem を使います。
import asyncio
from dmemfs import AsyncMemoryFileSystem
async def main():
mfs = AsyncMemoryFileSystem(max_quota=32 * 1024 * 1024)
await mfs.mkdir("/data")
# 非同期書き込み
async with await mfs.open("/data/result.bin", "wb") as f:
await f.write(b"async result data\n")
# 非同期読み込み
async with await mfs.open("/data/result.bin", "rb") as f:
content = await f.read()
print(content)
asyncio.run(main())
内部的には asyncio.Lock ベースの排他制御が入っているため、複数のコルーチンから同時にアクセスしても安全です。
並列ダウンロードと即時処理の例
import asyncio
import aiohttp
from dmemfs import AsyncMemoryFileSystem
async def fetch_and_process(session, url: str, mfs, path: str):
async with session.get(url) as resp:
data = await resp.read()
async with await mfs.open(path, "wb") as f:
await f.write(data)
async def main():
urls = [
("https://example.com/a.json", "/cache/a.json"),
("https://example.com/b.json", "/cache/b.json"),
("https://example.com/c.json", "/cache/c.json"),
]
mfs = AsyncMemoryFileSystem(max_quota=64 * 1024 * 1024)
await mfs.mkdir("/cache")
async with aiohttp.ClientSession() as session:
await asyncio.gather(*[
fetch_and_process(session, url, mfs, path)
for url, path in urls
])
# すべてのファイルがメモリ上に揃った状態で処理
for _, path in urls:
async with await mfs.open(path, "rb") as f:
data = await f.read()
print(f"{path}: {len(data)} bytes")
asyncio.run(main())
制限事項
D-MemFS はソフトウェア RAM ディスクとして非常に便利ですが、本物の RAM ディスクとは根本的に異なる制限があります。
| 制限 | 説明 |
|---|---|
| Python プロセス内限定 | 他のプロセスからはアクセスできない。外部コマンドや subprocess には使えない |
| 揮発性 | プロセスが終了すると内容はすべて消える(明示的に書き出すまでは永続化しない) |
| マウント不可 | ドライブレターやマウントポイントとして OS に見せることはできない |
| os.PathLike 非対応(意図的) | 仮想パスをホストパスと混同しないようにするための設計判断 |
こんな場面で使う
| ユースケース | 説明 |
|---|---|
| テスト |
pytest でファイルシステムを使うテストに。tmp_path フィクスチャの代わりに |
| CI | GitHub Actions や Azure Pipelines でディスク I/O を減らして高速化 |
| データ処理パイプライン | 中間ファイルをすべてメモリ上で完結させる |
| アーカイブの展開・処理 | zip / tar 等をメモリ上に展開し、ディスクへの書き出しなしに内部ファイルを処理 |
| センシティブなデータ処理 | ディスクに痕跡を残したくない一時処理 |
| 外部依存のないモック | ファイルシステムへのアクセスをテスト中にインターセプト |
アーカイブをメモリ上に展開する
zip ファイルをディスクに書き出さずに展開し、内部ファイルを処理する例です。
import io
import zipfile
from dmemfs import MemoryFileSystem
def process_zip(zip_bytes: bytes) -> dict[str, int]:
"""zip の内容をメモリ上の MFS に展開し、各ファイルのサイズを返す"""
mfs = MemoryFileSystem(max_quota=256 * 1024 * 1024)
mfs.mkdir("/extracted")
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
for name in zf.namelist():
data = zf.read(name)
with mfs.open(f"/extracted/{name}", "wb") as f:
f.write(data)
# MFS 上のファイルを処理(ここではサイズ一覧を返す)
result = {}
for entry in mfs.listdir("/extracted"):
path = f"/extracted/{entry}"
result[entry] = mfs.stat(path)["size"]
return result
tar.gz でも同様に使えます。
import io
import tarfile
from dmemfs import MemoryFileSystem
def process_targz(targz_bytes: bytes) -> list[str]:
"""tar.gz をメモリ上の MFS に展開し、ファイル一覧を返す"""
mfs = MemoryFileSystem(max_quota=512 * 1024 * 1024)
mfs.mkdir("/extracted")
with tarfile.open(fileobj=io.BytesIO(targz_bytes), mode="r:gz") as tf:
for member in tf.getmembers():
if member.isfile():
f = tf.extractfile(member)
if f is not None:
# ディレクトリ階層を再現
parts = member.name.split("/")
for depth in range(1, len(parts)):
d = "/extracted/" + "/".join(parts[:depth])
try:
mfs.mkdir(d)
except FileExistsError:
pass
with mfs.open(f"/extracted/{member.name}", "wb") as out:
out.write(f.read())
return mfs.listdir("/extracted")
ポイントは ディスクへの書き出しがゼロ な点です。ダウンロードしたアーカイブを一時ディレクトリに展開してから処理する従来のパターンと比べ、SSD の書き込み寿命を消耗しません。
pytest フィクスチャとして使う
import pytest
from dmemfs import MemoryFileSystem
@pytest.fixture
def mfs():
"""各テストに独立したインメモリ FS を提供する"""
return MemoryFileSystem(max_quota=32 * 1024 * 1024)
def test_write_and_read(mfs):
mfs.mkdir("/work")
with mfs.open("/work/test.txt", "wb") as f:
f.write(b"test data")
with mfs.open("/work/test.txt", "rb") as f:
assert f.read() == b"test data"
def test_quota_is_isolated(mfs):
"""各テストはクォータが独立している"""
mfs.mkdir("/data")
with mfs.open("/data/file.bin", "wb") as f:
f.write(b"x" * 1024)
st = mfs.stat("/data/file.bin")
assert st["size"] == 1024
おわりに
Windows での RAM ディスクに管理者権限が必要な問題は、Python コードの中だけなら D-MemFS で完全に回避できます。pip install D-MemFS して TemporaryDirectory を置き換えるだけで、SSD 上の tempfile と比べて劇的な速度改善が得られます。
外部コマンドを呼ぶ必要がない純粋な Python 処理なら、D-MemFS は最も手軽で高速な選択肢です。
🛡️ D-MemFS 活用ガイド(連載)
- 第1弾:BytesIO じゃダメな理由 — Python インメモリ FS ライブラリを作った話
- 第2弾:管理者権限不要でWindowsにRAMディスクを構築する ― Python環境のI/O高速化手法(本記事)
- 第3弾:PythonのOOMキルを完全防御する:BytesIOの罠とD-MemFS「ハードクォータ」の設計思想
- 第4弾:PythonでSQLiteの共有インメモリDBが消える仕様と、
deserialize()の落とし穴をbackup()で回避した話
📖 設計の裏側をもっと深く
なぜこのライブラリを作ったのか、その原体験や「バイブコーディング消耗からの脱却」としてのSDD(仕様駆動開発)の実践記録については、Zenn で連載しています。あわせてどうぞ。
GitHub で Star ⭐ をいただけると、開発の大きな励みになります!
(↓以下のリンク先がプロジェクトの本体です)