В Zen of Python есть много мудрых идей. Одна особенно полезная гласит: "Должен существовать один и, желательно, только один очевидный способ сделать это.". Тем не менее в Python существует несколько способов решить большинство задач. Например, есть разные способы чтения файла в Python, включая редко используемый модуль mmap.

Модуль mmap в Python предоставляет ввод/вывод (I/O) через отображение файлов в память. Он позволяет воспользоваться более низкоуровневой функциональностью операционной системы и работать с файлами как с одной большой строкой или массивом. Это может дать существенное улучшение производительности в коде, где требуется большое количество операций ввода-вывода с файлами.

В этом руководстве вы узнаете:

  • Какие виды компьютерной памяти существуют

  • Какие задачи можно решить с помощью mmap

  • Как использовать отображение в память для более быстрого чтения больших файлов

  • Как изменить часть файла, не перезаписывая весь файл

  • Как использовать mmap для обмена информацией между несколькими процессами

readme

Если вы нашли ошибку, то используйте ctrl + enter или пишите в лс. Спасибо.

Содержание

Память компьютера

Отображение в память — это приём, который использует низкоуровневые API операционной системы для загрузки файла непосредственно в оперативную память. Это может значительно повысить производительность ввода‑вывода файлов в вашей программе. Чтобы лучше понять, как отображение в память улучшает производительность и когда можно использовать модуль mmap для получения этих преимуществ, полезно сначала узнать немного о памяти компьютера.

Память компьютера — большая и сложная тема, но в этом руководстве рассматривается только то, что нужно знать для эффективного использования mmap. В рамках этого руководства термин «память» означает оперативную память (RAM).

Существуют несколько типов памяти:

  • Физическая

  • Виртуальная

  • Разделяемая

Каждый тип памяти может быть задействован при использовании отображения в память, поэтому рассмотрим их кратко.

Физическая память

Физическая память — самый простой для понимания тип памяти, поскольку она часто упоминается в маркетинге при продаже компьютеров. Физическая память обычно реализована в виде модулей, подключённых к материнской плате.

Физическая память — это объём энергозависимой памяти, доступной для программ во время их выполнения. Физическую память не следует путать с хранилищем данных, таким как жёсткий диск или твердотельный накопитель.

Виртуальная память

Виртуальная память — способ управления памятью. Операционная система использует виртуальную память, чтобы создать иллюзию большего объёма доступной памяти, позволяя вам меньше беспокоиться о реальном количестве RAM в любой момент времени. ОС использует части энергонезависимого хранилища (например, SSD) для эмуляции дополнительной оперативной памяти. Для этого операционной системе необходимо поддерживать отображение между физической и виртуальной памятью. Каждая ОС применяет собственный сложный алгоритм сопоставления виртуальных адресов с физическими с помощью структуры данных, называемой таблицей страниц.

К счастью, большая часть этой сложности скрыта от ваших программ. Вам не нужно разбираться в таблицах страниц или логико‑физическом отображении, чтобы писать код на Python. Тем не менее небольшое понимание работы памяти помогает лучше представлять, за что отвечают компьютер и библиотеки.

Модуль mmap в Python использует виртуальную память, создавая иллюзию загрузки очень большого файла в память, даже если содержимое файла не помещается в физическую память.

Разделяемая память

Разделяемая память — это ещё один механизм операционной системы, который позволяет нескольким программам одновременно получать доступ к одним и тем же данным. Разделяемая память может быть очень эффективным способом работы с данными в программах, использующих параллелизм.

Модуль mmap в Python использует разделяемую память для эффективного обмена большими объёмами данных между несколькими процессами, потоками и задачами Python, выполняющимися одновременно.

Углублённое изучение ввода-вывода файлов

Теперь, когда у вас есть общее представление о разных типах памяти, пора понять, что такое отображение в память и какие задачи оно решает. Отображение в память — это ещё один способ выполнения ввода‑вывода файлов, который может привести к лучшей производительности и эффективности использования памяти.

Чтобы полностью оценить преимущества отображения в память, полезно рассмотреть обычный файловый ввод‑вывод с более низкоуровневой точки зрения. При чтении файла под капотом за кулисами происходит много действий:

  • Передача управления ядру или основному коду операционной системы с помощью системных вызовов

  • Взаимодействие с физическим диском, на котором хранится файл

  • Копирование данных в разные буферы между пользовательским пространством и пространством ядра

Рассмотрим следующий код, который выполняет обычный файловый ввод‑вывод в Python:

def regular_io(filename):
    with open(filename, mode="r", encoding="utf8") as file_obj:
        text = file_obj.read()
        print(text)

Этот код читает весь файл в физическую память, если во время выполнения доступно достаточное количество памяти, и выводит его на экран.

Такой тип файлового ввода‑вывода вы, вероятно, изучали в начале работы с Python. Код простой и понятный. Однако то, что происходит внутри вызовов функций вроде read(), значительно сложнее. Помните, что Python — язык высокого уровня, поэтому большая часть этой сложности скрыта от программиста.

Системные вызовы

На самом деле вызов read() сигнализирует операционной системе выполнить множество сложной работы. К счастью, ОС предоставляет механизм абстрагирования конкретных деталей каждого аппаратного устройства от ваших программ — системные вызовы. Каждая операционная система реализует это по‑своему, но как минимум read() должен выполнить несколько системных вызовов, чтобы получить данные из файла.

Весь доступ к физическому оборудованию должен происходить в защищённой среде, называемой пространством ядра (kernel space). Системные вызовы — это API, которое предоставляет ОС, чтобы ваша программа могла перейти из пользовательского пространства (user space) в пространство ядра, где управляются низкоуровневые детали аппаратного обеспечения.

В случае read() требуется несколько системных вызовов, чтобы ОС взаимодействовала с физическим устройством хранения и вернула данные.

Вам не нужно глубоко разбираться в тонкостях системных вызовов и архитектуры компьютера, чтобы понять отображение в память. Главное — помнить, что системные вызовы относительно дороги с вычислительной точки зрения: чем меньше их выполняется, тем быстрее, вероятно, будет исполняться ваш код.

Кроме системных вызовов, вызов read() часто влечёт за собой множество потенциально ненужных операций копирования данных между буферами до тех пор, пока данные не попадут в вашу программу.

Обычно всё это происходит так быстро, что остаётся незаметным. Но все эти уровни добавляют задержки и могут замедлять программу. Здесь на помощь приходит отображение в память.

Оптимизации с помощью отображения в память

Один из способов избежать этой нагрузки — использовать файл, отображённый в память. Представьте отображение в память как процесс, при котором операции чтения и записи пропускают многие из упомянутых слоёв и отображают запрошенные данные напрямую в физическую память.

Подход ввода-вывода через отображение в память жертвует объёмом используемой памяти ради скорости — это классическая компромиссная зависимость «пространство — время». Тем не менее отображение в память не обязательно требует больше памяти, чем традиционный подход. Операционная система очень хитроумна: она загружает данные «лениво», по мере запроса, подобно генераторам в Python.

Кроме того, благодаря виртуальной памяти вы можете загрузить файл, больший, чем ваша физическая память. Однако вы не получите больших приростов производительности от отображения в память, если для файла недостаточно физической памяти: тогда операционная система будет использовать более медленное энергонезависимое хранилище (например, SSD) для эмуляции недостающей оперативной памяти.

Чтение файла, отображённого в память, с помощью Python-модуля mmap

Итак, после всей этой теории вы, возможно, спрашиваете себя: «Как использовать mmap в Python для создания файла, отображённого в память?»

Вот эквивалентный пример кода с отображением в память для того файлового ввода‑вывода, который вы видели ранее:

import mmap

def mmap_io(filename):
    with open(filename, mode="r", encoding="utf8") as file_obj:
        with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ) as mmap_obj:
            text = mmap_obj.read()
            print(text)

Этот код читает весь файл в память как строку и выводит её на экран, так же, как и предыдущий подход с обычным файловым вводом‑выводом.

Кратко, использование mmap похоже на традиционный способ чтения файла, с несколькими отличиями:

  • Открыть файл через open() недостаточно. Нужно вызвать mmap.mmap(), чтобы сообщить ОС, что файл нужно отобразить в RAM.

  • Убедитесь, что режим, переданный в open(), совместим с режимом mmap.mmap(). По умолчанию open() открывает файл только для чтения, а mmap.mmap() по умолчанию — для чтения и записи, поэтому при открытии файла нужно указать режим явно.

  • Выполняйте все операции чтения и записи через объект mmap, а не через стандартный файловый объект, возвращаемый open().

Последствия для производительности

Подход с отображением в память немного сложнее типичного файлового ввода‑вывода, поскольку требует создания дополнительного объекта. Однако это небольшое изменение может привести к значительным улучшениям производительности при чтении файла всего в несколько мегабайт. Ниже приведено сравнение чтения сырого текста известного романа «История Дон Кихота», который занимает примерно 2,4 мегабайта:

>>> import timeit
>>> timeit.repeat(
...     "regular_io(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import regular_io, filename")
[0.02022400000000002, 0.01988580000000001, 0.020257300000000006]
>>> timeit.repeat(
...     "mmap_io(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import mmap_io, filename")
[0.006156499999999981, 0.004843099999999989, 0.004868600000000001]

Этот код измеряет время чтения целого файла размером 2,4 МБ с использованием обычного файлового ввода‑вывода и ввода‑вывода с отображением в память. Как видно, подход с отображением в память занимает примерно 0,005 с против почти 0,02 с для обычного подхода. При чтении более крупного файла этот прирост производительности может быть ещё больше.

Примечание: эти результаты получены на Windows 10 и Python 3.8. Поскольку отображение в память сильно зависит от реализаций в ОС, ваши результаты могут отличаться.

API объекта файла mmap в Python очень похож на традиционный файловый объект, но с одной дополнительной "суперсилой": объект mmap можно нарезать (slicing) так же, как строки!

Создание объекта mmap

При создании объекта mmap есть несколько тонкостей, на которые стоит обратить внимание:

mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ)

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

Второй аргумент mmap — length=0. Это длина отображения в байтах. 0 — специальное значение, означающее, что система должна создать отображение достаточного размера для размещения всего файла.

Аргумент access указывает ОС, как вы собираетесь взаимодействовать с отображённой памятью. Варианты: ACCESS_READ, ACCESS_WRITE, ACCESS_COPY и ACCESS_DEFAULT. Они отчасти соответствуют аргументам mode в встроенном open():

  • ACCESS_READ создаёт отображение только для чтения.

  • ACCESS_DEFAULT по умолчанию принимает режим, указанный в необязательном аргументе prot, который отвечает за защиту памяти.

  • ACCESS_WRITE и ACCESS_COPY — два режима записи, о которых будет рассказано ниже.

Аргументы file descriptor, length и access — минимальный набор, необходимый для создания кроссплатформенного отображения файла (Windows, Linux, macOS). Код, использующий их, будет работать через интерфейс memory‑mapping на всех ОС без необходимости знать, где он выполняется.

Ещё один полезный аргумент — offset, который может сэкономить память. Он указывает mmap создать отображение, начинающееся с указанного смещения в файле.

Объекты mmap как строки

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

import mmap

def mmap_io(filename):
    with open(filename, mode="r", encoding="utf8") as file_obj:
        with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ) as mmap_obj:
            print(mmap_obj[10:20])

Этот код выводит на экран десять символов из mmap_obj и также загружает эти десять символов в физическую память. Как и прежде, данные читаются лениво.

Срез (slice) не продвигает внутреннюю позицию файла. Поэтому если после среза вызвать read(), чтение всё ещё начнётся с начала файла.

Поиск в файле, отображённом в память

Кроме срезов, mmap поддерживает и другие строкоподобные операции, например find() и rfind() для поиска текста в файле. Ниже — два подхода для поиска первого вхождения "the" в файле:

import mmap

def regular_io_find(filename):
    with open(filename, mode="r", encoding="utf-8") as file_obj:
        text = file_obj.read()
        print(text.find(" the "))

def mmap_io_find(filename):
    with open(filename, mode="r", encoding="utf-8") as file_obj:
        with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ) as mmap_obj:
            print(mmap_obj.find(b" the "))

Обе функции ищут первое вхождение "the". Главное различие в том, что первая использует find() у строкового объекта, а вторая — у объекта файла, отображённого в память.

Примечание: mmap работает с байтами, а не со строками.

Вот разница в производительности:

>>> import timeit
>>> timeit.repeat(
...     "regular_io_find(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import regular_io_find, filename")
[0.01919180000000001, 0.01940510000000001, 0.019157700000000027]
>>> timeit.repeat(
...     "mmap_io_find(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import mmap_io_find, filename")
[0.0009397999999999906, 0.0018005999999999855, 0.000826699999999958]

Это разница в производительности на несколько порядков! Опять же, результаты могут отличаться в зависимости от операционной системы.

Файлы, отображённые в память, также можно использовать напрямую с регулярными выражениями. Рассмотрим пример, который находит и выводит все пятибуквенные слова:

import re
import mmap

def mmap_io_re(filename):
    five_letter_word = re.compile(rb"\b[a-zA-Z]{5}\b")

    with open(filename, mode="r", encoding="utf-8") as file_obj:
        with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ) as mmap_obj:
            for word in five_letter_word.findall(mmap_obj):
                print(word)

Этот код читает весь файл и выводит каждое слово, состоящее ровно из пяти букв. Помните, что файлы, отображённые в память, работают с байтовыми строками, поэтому регулярные выражения также должны использовать байтовые строки.

Вот эквивалентный код с использованием обычного файлового ввода‑вывода:

import re

def regular_io_re(filename):
    five_letter_word = re.compile(r"\b[a-zA-Z]{5}\b")

    with open(filename, mode="r", encoding="utf-8") as file_obj:
        for word in five_letter_word.findall(file_obj.read()):
            print(word)

Этот код также выводит все слова из пяти символов в файле, но использует традиционный механизм ввода‑вывода вместо отображения в память. Как и раньше, производительность двух подходов различается:

>>> import timeit
>>> timeit.repeat(
...     "regular_io_re(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import regular_io_re, filename")
[0.10474110000000003, 0.10358619999999996, 0.10347820000000002]
>>> timeit.repeat(
...     "mmap_io_re(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import mmap_io_re, filename")
[0.0740976000000001, 0.07362639999999998, 0.07380980000000004]

Подход с отображением в память по‑прежнему быстрее примерно на порядок.

Объекты, отображённые в память, как файлы

Объекты, отображённые в память, одновременно похожи на строки и на файлы, поэтому mmap позволяет выполнять обычные файловые операции, такие как seek(), tell() и readline(). Эти функции работают точно так же, как и у обычных файловых объектов.

Например, так можно перейти к определённой позиции в файле и затем выполнить поиск слова:

import mmap

def mmap_io_find_and_seek(filename):
    with open(filename, mode="r", encoding="utf-8") as file_obj:
        with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ) as mmap_obj:
            mmap_obj.seek(10000)
            mmap_obj.find(b" the ")

Этот код перейдёт к позиции 10 000 в файле, а затем найдёт первое вхождение " the ".

seek() работает с файлами, отображёнными в память, так же, как и с обычными файлами:

def regular_io_find_and_seek(filename):
    with open(filename, mode="r", encoding="utf-8") as file_obj:
        file_obj.seek(10000)
        text = file_obj.read()
        text.find(" the ")

Код для обоих подходов очень похож. Посмотрим, как они соотносятся по производительности:

>>> import timeit
>>> timeit.repeat(
...     "regular_io_find_and_seek(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import regular_io_find_and_seek, filename")
[0.019396099999999916, 0.01936059999999995, 0.019192100000000045]
>>> timeit.repeat(
...     "mmap_io_find_and_seek(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import mmap_io_find_and_seek, filename")
[0.000925100000000012, 0.000788299999999964, 0.0007854999999999945]

Снова: после всего нескольких небольших правок кода подход с отображением в память оказывается значительно быстрее.

Запись в файл, отображённый в память, с помощью Python-модуля mmap

Отображение в память наиболее полезно для чтения файлов, но его также можно использовать для записи. API mmap для записи файлов очень похож на обычный файловый ввод‑вывод, за некоторыми отличиями.

Ниже пример записи текста в файл, отображённый в память:

import mmap

def mmap_io_write(filename, text):
    with open(filename, mode="w", encoding="utf-8") as file_obj:
        with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_WRITE) as mmap_obj:
            mmap_obj.write(text)

Этот код записывает текст в файл, отображённый в память. Однако он вызовет исключение ValueError, если файл пуст в момент создания объекта mmap.

Модуль mmap в Python не позволяет отображать пустой файл в память. Это разумно, поскольку концептуально пустой файл, отображённый в память, — это просто буфер памяти, и объект отображения в память не нужен.

Обычно отображение в память используется в режимах чтения или чтения/записи. Например, следующий код показывает как быстро прочитать файл и изменить только его часть:

import mmap

def mmap_io_write(filename):
    with open(filename, mode="r+") as file_obj:
        with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_WRITE) as mmap_obj:
            mmap_obj[10:16] = b"python"
            mmap_obj.flush()

Эта функция откроет файл, который уже содержит как минимум шестнадцать символов, и заменит символы с 10 по 15 на "python".

Изменения, внесённые в mmap_obj, видимы как в памяти, так и в файле на диске. Официальная документация Python рекомендует всегда вызывать flush(), чтобы гарантировать запись данных на диск.

Режимы записи

Семантика операций записи контролируется параметром access. В отличие от обычных файлов, для mmap доступны два варианта, определяющие поведение записи:

  • ACCESS_WRITE — «write‑through»: изменения записываются в память и сохраняются на диск.

  • ACCESS_COPY — изменения записываются только в память и не записываются в исходный файл (даже при вызове flush()).

Иными словами, ACCESS_WRITE пишет и в память, и в файл, а ACCESS_COPY — только в память.

Поиск и замена текста

Данные в файле, отображённом в память, представлены в виде строки байтов, но при этом эти байты изменяемы. Это делает более простым и эффективным код для поиска и замены данных в файле:

import mmap
import os
import shutil

def regular_io_find_and_replace(filename):
    with open(filename, "r", encoding="utf-8") as orig_file_obj:
        with open("tmp.txt", "w", encoding="utf-8") as new_file_obj:
            orig_text = orig_file_obj.read()
            new_text = orig_text.replace(" the ", " eht ")
            new_file_obj.write(new_text)

    shutil.copyfile("tmp.txt", filename)
    os.remove("tmp.txt")

def mmap_io_find_and_replace(filename):
    with open(filename, mode="r+", encoding="utf-8") as file_obj:
        with mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_WRITE) as mmap_obj:
            orig_text = mmap_obj.read()
            new_text = orig_text.replace(b" the ", b" eht ")
            mmap_obj[:] = new_text
            mmap_obj.flush()

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

В данном случае подход с mmap оказывается немного медленнее для такого размера файла. Поэтому полный поиск с заменой в файле, отображённом в память, может быть не самым эффективным решением — всё зависит от многих факторов: длины файла, скорости оперативной памяти вашей машины и тому подобное Также возможен эффект кэширования со стороны операционной системы, который искажает измерения времени. Как видно, при последовательных вызовах традиционный IO подход ускорялся.

>>> import timeit
>>> timeit.repeat(
...     "regular_io_find_and_replace(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import regular_io_find_and_replace, filename")
[0.031016973999996367, 0.019185273000005054, 0.019321329999996806]
>>> timeit.repeat(
...     "mmap_io_find_and_replace(filename)",
...     repeat=3,
...     number=1,
...     setup="from __main__ import mmap_io_find_and_replace, filename")
[0.026475408999999672, 0.030173652999998524, 0.029132930999999473]

В этом простом сценарии поиска и замены отображение в память даёт чуть более компактный код, но не всегда существенно ускоряет выполнение.

Обмен данными между процессами с помощью Python-mmap

До сих пор вы использовали отображение в память только для данных на диске. Однако можно создавать анонимные отображения памяти, не связанные с физическим хранилищем. Для этого передают -1 в качестве файлового дескриптора:

import mmap

with mmap.mmap(-1, length=100, access=mmap.ACCESS_WRITE) as mmap_obj:
    mmap_obj[0:100] = b"a" * 100
    print(mmap_obj[0:100])

Это создаёт анонимный объект, отображённый в память (RAM), содержащий 100 копий буквы "a".

Анонимный объект mmap — по сути буфер заданного размера (параметр length) в памяти. Этот буфер похож на io.StringIO или io.BytesIO из стандартной библиотеки, но в отличие от них анонимный mmap поддерживает совместное использование между процессами.

Это значит, что можно использовать анонимные объекты mmap для обмена данными между процессами, даже если у процессов полностью раздельная память и стеки. Ниже пример создания анонимного mmap для обмена данными, доступными для записи и чтения из обоих процессов:

import mmap

def sharing_with_mmap():
    BUF = mmap.mmap(-1, length=100, access=mmap.ACCESS_WRITE)

    pid = os.fork()
    if pid == 0:
        # Child process
        BUF[0:100] = b"a" * 100
    else:
        time.sleep(2)
        print(BUF[0:100])

С этим кодом вы создаёте отображённый в память буфер размером 100 байт и позволяете этому буферу быть доступным для чтения и записи из обоих процессов. Такой подход полезен, если нужно экономить память и одновременно делиться большими объёмами данных между процессами.

Преимущества совместного использования памяти через mmap:

  • Данные не нужно копировать между процессами.

  • Операционная система прозрачно управляет памятью.

  • Данные не нужно сериализовать (pickle) при передаче между процессами, что экономит время ЦП.

Говоря о сериализации, стоит отметить, что mmap несовместим с более высокоуровневыми и функциональными API, такими как встроенный модуль multiprocessing. multiprocessing требует, чтобы передаваемые между процессами данные поддерживали протокол pickle, чего mmap не поддерживает.

Возможно, вам захочется использовать multiprocessing вместо os.fork(), например так:

from multiprocessing import Process

def modify(buf):
    buf[0:100] = b"xy" * 50

if __name__ == "__main__":
    BUF = mmap.mmap(-1, length=100, access=mmap.ACCESS_WRITE)
    BUF[0:100] = b"a" * 100
    p = Process(target=modify, args=(BUF,))
    p.start()
    p.join()
    print(BUF[0:100])

Здесь вы пытаетесь создать новый процесс и передать ему объект, отображённый в память. Этот код сразу же вызовет TypeError, поскольку объект mmap нельзя сериализовать (pickle), а сериализация требуется для передачи данных во второй процесс. Следовательно, для совместного использования mmap придётся обходиться более низкоуровневыми средствами, такими как os.fork().

Если вы используете Python 3.8 или новее, можно использовать новый модуль shared_memory для более удобного и эффективного обмена данными между процессами:

from multiprocessing import Process
from multiprocessing import shared_memory

def modify(buf_name):
    shm = shared_memory.SharedMemory(buf_name)
    shm.buf[0:50] = b"b" * 50
    shm.close()

if __name__ == "__main__":
    shm = shared_memory.SharedMemory(create=True, size=100)

    try:
        shm.buf[0:100] = b"a" * 100
        proc = Process(target=modify, args=(shm.name,))
        proc.start()
        proc.join()
        print(bytes(shm.buf[:100]))
    finally:
        shm.close()
        shm.unlink()

Эта небольшая программа создаёт список из 100 символов и изменяет первые 50 из другого процесса.

Обратите внимание: второму процессу передаётся только имя буфера. Затем второй процесс может получить тот же блок памяти по этому уникальному имени. Это особенность модуля shared_memory, реализованная на основе mmap. Внутри модуль shared_memory использует уникальные для каждой ОС API для создания именованных отображений памяти.

Теперь вы знаете некоторые внутренние детали реализации новой функции shared_memory, добавленной в Python 3.8, а также как использовать mmap напрямую!

Заключение

Отображение в память — это альтернативный подход к файловому вводу‑выводу, доступный в Python через модуль mmap. Оно использует низкоуровневые API операционной системы для размещения содержимого файлов напрямую в оперативной памяти. Такой подход часто даёт улучшение производительности ввода‑вывода, поскольку устраняет многие дорогостоящие системные вызовы и уменьшает количество операций копирования данных между буферами.

В этом руководстве вы узнали:

  • В чём различия между физической, виртуальной и разделяемой памятью

  • Как оптимизировать использование памяти с помощью отображения в память

  • Как использовать модуль mmap в Python для реализации отображения в память в своём коде

API mmap похож на обычный файловый API, поэтому опробовать его довольно просто. Попробуйте применить mmap в своём коде, чтобы выяснить, принесёт ли вашему приложению улучшение производительности.

Контакты автора статьи
Автор кдпв