4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

管理者権限不要でWindowsにRAMディスクを構築する ― Python環境のI/O高速化手法

4
Last updated at Posted at 2026-03-09

はじめに

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 つです。

  1. 高速な一時ファイル処理 — ディスク I/O のレイテンシを排除して処理を速くしたい
  2. ディスクへの書き込みを避けたい — 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 活用ガイド(連載)

📖 設計の裏側をもっと深く
なぜこのライブラリを作ったのか、その原体験や「バイブコーディング消耗からの脱却」としてのSDD(仕様駆動開発)の実践記録については、Zenn で連載しています。あわせてどうぞ。

GitHub で Star ⭐ をいただけると、開発の大きな励みになります!
(↓以下のリンク先がプロジェクトの本体です)

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?