mmap – メモリマップファイル

目的:直接的なコンテンツ読み込みの代替となるメモリマップファイル
利用できるバージョン:2.1以上

ファイルをメモリマッピングすることは、通常の I/O 関数の代わりに、直接ファイルシステム上のデータにアクセスするためにオペレーティングシステムの仮想的なメモリシステムを使用します。メモリマッピングは、アクセス毎に独立したシステムコールを呼び出さず、バッファ間でデータのコピーが必要ない(メモリに直接アクセスされる)ので一般的に I/O 性能が向上します。

メモリマップファイルは、あなたの必要性に応じて可変文字列、ファイルのようなオブジェクトとして扱うことができます。メモリマップファイルは close(), flush(), read(), readline(), seek(), tell()write() のようなファイル API メソッドをサポートします。さらにスライシングや find() メソッドのような文字列 API もサポートします。

全てのサンプルは Lorem Ipsum を少し含む lorem.txt を使用します。参考までにテキストファイルは次になります。

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec
egestas, enim et consectetuer ullamcorper, lectus ligula rutrum leo, a
elementum elit tortor eu quam. Duis tincidunt nisi ut ante. Nulla
facilisi. Sed tristique eros eu libero. Pellentesque vel arcu. Vivamus
purus orci, iaculis ac, suscipit sit amet, pulvinar eu,
lacus. Praesent placerat tortor sed nisl. Nunc blandit diam egestas
dui. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Aliquam viverra fringilla
leo. Nulla feugiat augue eleifend nulla. Vivamus mauris. Vivamus sed
mauris in nibh placerat egestas. Suspendisse potenti. Mauris massa. Ut
eget velit auctor tortor blandit sollicitudin. Suspendisse imperdiet
justo.

Note

Unix と Windows では mmap() への引数とその振る舞いが違います。その違いについてはあまり説明しませんが、詳細はライブラリドキュメントを参照してください。

読み込み

メモリマップファイルを作成するために mmap() 関数を使ってみましょう。最初の引数はファイルディスクリプタで file オブジェクトの fileno() メソッドか、又は os.open() が返すファイルオブジェクトになります。 mmap() を呼び出す前にファイルをオープンすると、不要になったときにそのファイルをクローズする責任があることに注意してください。

mmap() への2番目の引数はマッピングするファイルのバイトサイズになります。もし2番目の引数に渡す値が 0 なら、ファイル全体がマッピングされます。もし2番目の引数のサイズがオープンするファイルのファイルサイズよりも大きい場合、そのファイルは拡張されます。

Note

Windows ではファイルサイズがゼロのマッピングを作成することはできません。

オプションのキーワード引数である access は、両方のプラットホームに対応しています。read-only には ACCESS_READ を、write-through (直接ファイルへ書き込まれるようにメモリを割り当てる)には ACCESS_WRITE を、copy-on-write (ファイルへ書き込まれないようにメモリを割り当てる)には const:ACCESS_COPY を使用してください。

import mmap
import contextlib

with open('lorem.txt', 'r') as f:
    with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) as m:
        print 'First 10 bytes via read :', m.read(10)
        print 'First 10 bytes via slice:', m[:10]
        print '2nd   10 bytes via read :', m.read(10)

ファイルポインタはスライシング操作によってアクセスされた最後のバイト位置を追跡します。このサンプルでは、最初の読み込みの後、そのポインタが10バイト前に進みます。それからスライシング操作によりファイルの先頭にリセットされて、再度スライスされて10バイト前に進みます。スライシング操作の後、再度 read() を呼び出すとファイルの11-20バイトの部分を読み込みます。

$ python mmap_read.py
First 10 bytes via read : Lorem ipsu
First 10 bytes via slice: Lorem ipsu
2nd   10 bytes via read : m dolor si

書き込み

更新を受け取るようにメモリマップファイルを設定するために、そのファイルをマッピングする前に読み書きモード 'r+' ('w' ではない) でオープンしてください。それから、そのデータを変更する様々な API メソッド(write() やスライスの割当等)を使用してください。

ACCESS_WRITE のデフォルトアクセスモードを使用して、ある行の一部を変更するスライスを割り当てる例があります。

import mmap
import shutil
import contextlib

# サンプルファイルをコピーする
shutil.copyfile('lorem.txt', 'lorem_copy.txt')

word = 'consectetuer'
reversed = word[::-1]
print 'Looking for    :', word
print 'Replacing with :', reversed

with open('lorem_copy.txt', 'r+') as f:
    with contextlib.closing(mmap.mmap(f.fileno(), 0)) as m:
        print 'Before:', m.readline().rstrip()
        m.seek(0) # rewind

        loc = m.find(word)
        m[loc:loc+len(word)] = reversed
        m.flush()

        m.seek(0) # rewind
        print 'After :', m.readline().rstrip()

単語 “consectetuer” は最初の行の中間で置換されました。

$ python mmap_write_slice.py
Looking for    : consectetuer
Replacing with : reutetcesnoc
Before: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec
After : Lorem ipsum dolor sit amet, reutetcesnoc adipiscing elit. Donec

ACCESS_COPY モード

ディスク上のファイルに変更を書き込まないようにするには ACCESS_COPY モードを使用してください。

import mmap
import shutil
import contextlib

# サンプルファイルをコピーする
shutil.copyfile('lorem.txt', 'lorem_copy.txt')

word = 'consectetuer'
reversed = word[::-1]

with open('lorem_copy.txt', 'r+') as f:
    with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_COPY)) as m:
        print 'Memory Before:', m.readline().rstrip()
        print 'File Before  :', f.readline().rstrip()
        print

        m.seek(0) # rewind
        loc = m.find(word)
        m[loc:loc+len(word)] = reversed

        m.seek(0) # rewind
        print 'Memory After :', m.readline().rstrip()

        f.seek(0)
        print 'File After   :', f.readline().rstrip()

このサンプルでは、mmap ハンドラとは別にファイルハンドラを巻き戻す必要があります。それは2つのオブジェクトの内部状態が独立して保持されるからです。

$ python mmap_write_copy.py
Memory Before: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec
File Before  : Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec

Memory After : Lorem ipsum dolor sit amet, reutetcesnoc adipiscing elit. Donec
File After   : Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec

正規表現

メモリマップファイルは文字列のように動作するので、正規表現などの文字列を操作する他のモジュールと一緒に使用できます。この例は “nulla” を含む全ての文を見つけます。

import mmap
import re
import contextlib

pattern = re.compile(r'(\.\W+)?([^.]?nulla[^.]*?\.)',
                     re.DOTALL | re.IGNORECASE | re.MULTILINE)

with open('lorem.txt', 'r') as f:
    with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) as m:
        for match in pattern.findall(m):
            print match[1].replace('\n', ' ')

正規表現でマッチさせる2つのグループを含むパターンなので findall() からの戻り値はタプルのシーケンスになります。 print 文は改行をスペースに置換した match の文を取り出して、その結果を1行で表示します。

$ python mmap_regex.py
Nulla facilisi.
Nulla feugiat augue eleifend nulla.

See also

mmap
本モジュールの標準ライブラリドキュメント
os
os モジュール
contextlib
メモリマップファイルのコンテキストマネージャを作成するには closing() 関数を使用してください
re
正規表現
Bookmark and Share