Mmap.mmap: примеры (PYTHON)

Работа с отображением памяти в Python через mmap.mmap
Раздел: Память, Низкоуровневые операции
mmap.mmap(fileno: int, length: int, **kwargs): mmap object

Функция mmap.mmap в Python

Функция mmap.mmap() создает объект отображения памяти, который позволяет работать с файлами или ресурсами, как с большими байтовыми массивами в оперативной памяти. Она предоставляет интерфейс для чтения и записи данных, используя механизмы виртуальной памяти операционной системы, что часто ускоряет доступ к большим файлам.

Использование этого метода целесообразно при обработке объемных файлов, когда полная загрузка в память нежелательна или невозможна. Это также полезно для разделяемой памяти между процессами.

Сигнатура функции выглядит так:

mmap.mmap(fileno, length, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ|mmap.PROT_WRITE, access=mmap.ACCESS_DEFAULT, offset=0)

Аргументы:

  • fileno (целое число): Целочисленный файловый дескриптор, полученный методом fileno() файлового объекта, либо константа -1 для создания анонимного отображения.
  • length (целое число): Размер отображаемой области в байтах. Для файлов часто используют 0, чтобы отобразить весь файл.
  • flags (целое число, опционально): Указывает тип отображения. По умолчанию mmap.MAP_SHARED (изменения видны другим процессам). Альтернатива - mmap.MAP_PRIVATE (копия при записи).
  • prot (целое число, опционально): Задает защиту памяти. По умолчанию mmap.PROT_READ | mmap.PROT_WRITE (чтение и запись). Возможны комбинации mmap.PROT_READ, mmap.PROT_WRITE, mmap.PROT_EXEC.
  • access (целое число, опционально): Альтернативный способ задания доступа. Используется вместо prot и flags. Варианты: mmap.ACCESS_READ (только чтение), mmap.ACCESS_WRITE (чтение и запись, сквозная запись), mmap.ACCESS_COPY (копия при записи).
  • offset (целое число, опционально): Смещение от начала файла, с которого начинается отображение. Должно быть кратно размеру страницы памяти (обычно 4096).

Возвращаемое значение:

Функция возвращает объект mmap.mmap, который поддерживает интерфейс, схожий с объектом bytes или bytearray. С ним можно работать как с изменяемой последовательностью байтов, используя срезы, методы поиска и замены.

Короткие примеры использования

Пример 1: Чтение файла с доступом только для чтения.

import mmap

with open('example.txt', 'r+b') as f:
    with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
        # Чтение первых 10 байт
        data = mm[:10]
        print(data)
b'First line'

Пример 2: Запись в файл с использованием отображения.

import mmap

with open('example.txt', 'r+b') as f:
    with mmap.mmap(f.fileno(), 0) as mm:
        # Замена слова в файле
        if mm.find(b'old') != -1:
            mm.seek(mm.find(b'old'))
            mm.write(b'new')

Пример 3: Анонимное отображение для обмена данными между процессами.

import mmap, os

# Создание анонимной области памяти размером 100 байт
mm = mmap.mmap(-1, 100)
mm.write(b'Data for other process')
mm.seek(0)
print(mm.read(30))
b'Data for other process'

Похожие функции в Python

В Python существуют другие методы для работы с данными, которые могут служить альтернативой в определенных сценариях.

  • open().read() / write(): Стандартное чтение и запись файлов. Предпочтительнее для небольших файлов или последовательного доступа, когда не требуется случайный доступ к большим блокам данных. Не создает нагрузку на управление памятью ОС.
  • bytearray / memoryview: Встроенные типы для работы с бинарными данными в памяти. Используются, когда данные уже загружены в оперативную память и требуют изменений. memoryview позволяет создавать представления без копирования данных.
  • numpy.memmap: Функция из библиотеки NumPy для отображения файлов в память как многомерных массивов. Является предпочтительным выбором для числовых данных и научных вычислений, так как предоставляет удобный интерфейс для работы с массивами.
  • multiprocessing.Array / Value: Примитивы разделяемой памяти для межпроцессного взаимодействия. Более высокоуровневые, чем анонимное отображение mmap, и часто проще в использовании для конкретных типов данных.

Альтернативы в других языках программирования

Концепция отображения файлов в память присутствует во многих языках, но реализация и API различаются.

C# (.NET): Класс MemoryMappedFile из пространства имен System.IO.MemoryMappedFiles.

using System.IO.MemoryMappedFiles;
using System.IO;

// Создание отображенного файла
using (var mmf = MemoryMappedFile.CreateFromFile("data.dat"))
using (var accessor = mmf.CreateViewAccessor())
{
    byte value = accessor.ReadByte(0);
    accessor.Write(0, (byte)42);
}

Java: Класс java.nio.channels.FileChannel с методом map().

import java.nio.*;
import java.nio.channels.FileChannel;
import java.io.RandomAccessFile;

RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());

buffer.put(0, (byte) 'A');
byte b = buffer.get(0);

Golang: Пакет syscall или golang.org/x/exp/mmap. В Go подход более низкоуровневый.

import "golang.org/x/exp/mmap"

at, _ := mmap.Open("data.bin")
defer at.Close()

data := make([]byte, 10)
at.ReadAt(data, 0) // Чтение по смещению

JavaScript/Node.js: Нет прямой поддержки в стандартной библиотеке. Для работы с большими файлами используют потоки (Streams API) или модули типа fs.open с чтением в буфер.

PHP: Функция mmap() доступна через модуль ext/mmap (нестандартный). Чаще используют функции вроде fopen и fread.

Ключевое отличие Python-реализации - удобный объектно-ориентированный интерфейс, интегрированный с контекстными менеджерами (with), что обеспечивает автоматическое закрытие.

Типичные ошибки при использовании

1. Некорректный файловый дескриптор. Передача неверного или закрытого дескриптора.

import mmap

f = open('test.txt', 'r')
f.close()
try:
    mm = mmap.mmap(f.fileno(), 0)
except ValueError as e:
    print(f'Ошибка: {e}')
Ошибка: cannot mmap an empty file

2. Ошибка размера (length=0 для пустого файла). Попытка отобразить файл нулевой длины без указания размера для анонимного отображения.

import mmap

open('empty.txt', 'w').close() # Создаем пустой файл
with open('empty.txt', 'r+b') as f:
    try:
        mm = mmap.mmap(f.fileno(), 0)
    except ValueError as e:
        print(f'Ошибка: {e}')
Ошибка: cannot mmap an empty file

3. Попытка записи при открытии с доступом только для чтения (ACCESS_READ).

import mmap

with open('test.txt', 'w+b') as f:
    f.write(b'sample')
    f.flush()
    with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
        try:
            mm[0] = 65
        except TypeError as e:
            print(f'Ошибка: {e}')
Ошибка: mmap can't modify a readonly memory map.

4. Неверное смещение (offset), не кратное размеру страницы.

import mmap

with open('test.txt', 'w+b') as f:
    f.write(b'x' * 5000)
    try:
        # Смещение не кратно размеру страницы (например, 1000)
        mm = mmap.mmap(f.fileno(), 100, offset=1000)
    except ValueError as e:
        print(f'Ошибка: {e}')
Ошибка: mmap offset must be a multiple of the page size (4096)

Изменения в последних версиях Python

В Python 3.8 были добавлены новые методы для объектов mmap: madvise() и shrink().

  • madvise() позволяет давать подсказки ядру ОС о предполагаемом шаблоне доступа к памяти (например, mmap.MADV_RANDOM для случайного доступа), что может оптимизировать производительность.
  • shrink() пытается уменьшить размер отображения, освобождая неиспользуемую память.

В Python 3.10 появилась возможность использовать константу mmap.MAP_POPULATE (Linux) в аргументе flags для предварительной загрузки страниц в память, что может уменьшить количество page faults при последующем доступе.

Также в более ранних версиях были улучшены сообщения об ошибках и документация.

Расширенные примеры использования

Пример 1: Многопроцессное взаимодействие через анонимную память. Отображение памяти может служить разделяемой памятью для процессов.

Пример python
import mmap, os
from multiprocessing import Process

def writer():
    with mmap.mmap(-1, 100, tagname='shared_mem') as mm:
        mm.write(b'Hello from writer!')
        # Ждем, пока читатель прочитает
        import time
        time.sleep(2)

def reader():
    import time
    time.sleep(1)  # Ждем, пока запишется
    # Открываем существующее отображение по имени
    with mmap.mmap(-1, 100, tagname='shared_mem') as mm:
        mm.seek(0)
        data = mm.read(50)
        print(f'Reader получил: {data}')

if __name__ == '__main__':
    p1 = Process(target=writer)
    p2 = Process(target=reader)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
Reader получил: b'Hello from writer!'

Пример 2: Работа со структурированными данными с использованием модуля struct.

Пример python
import mmap, struct

with open('data.bin', 'w+b') as f:
    # Записываем 3 целых числа
    f.write(struct.pack('iii', 10, 20, 30))
    f.flush()
    with mmap.mmap(f.fileno(), 0) as mm:
        # Читаем второе число (смещение 4 байта, т.к. int обычно 4 байта)
        second_number = struct.unpack('i', mm[4:8])[0]
        print(f'Второе число: {second_number}')
        # Меняем третье число
        mm[8:12] = struct.pack('i', 99)
Второе число: 20

Пример 3: Поиск и замена всех вхождений подстроки в большом файле.

Пример python
import mmap, os

def replace_in_file(filename, old, new):
    if len(old) != len(new):
        raise ValueError('Длины строк должны совпадать')
    with open(filename, 'r+b') as f:
        with mmap.mmap(f.fileno(), 0) as mm:
            pos = 0
            while True:
                pos = mm.find(old.encode(), pos)
                if pos == -1:
                    break
                mm.seek(pos)
                mm.write(new.encode())
                pos += len(old)

# Тестирование
with open('big.txt', 'w') as f:
    f.write('cat dog cat bird cat\n' * 10000)

replace_in_file('big.txt', 'cat', 'CAT')

# Проверка первых 100 байт
with open('big.txt', 'rb') as f:
    print(f.read(100))
b'CAT dog CAT bird CAT\nCAT dog CAT bird CAT\nCAT dog CAT bird CAT\nCAT dog CAT bird CAT\nCAT do'

Пример 4: Использование метода madvise() для оптимизации (Python 3.8+).

Пример python
import mmap

with open('huge.bin', 'r+b') as f:
    with mmap.mmap(f.fileno(), 0) as mm:
        # Сообщаем ядру, что доступ будет последовательным
        mm.madvise(mmap.MADV_SEQUENTIAL)
        # Читаем данные последовательно
        data = mm.read(1024*1024)  # 1 МБ
        # После завершения чтения, можно освободить память
        mm.madvise(mmap.MADV_DONTNEED)

питон mmap.mmap function comments

En
Mmap.mmap Memory-map file