Почему Python mmap не работает с большими файлами?
[Редактировать: эта проблема относится только к 32-битным системам. Если ваш компьютер, ваша ОС и ваша реализация на python 64-битные, то создание больших файлов работает надежно и чрезвычайно эффективно.]
Я пишу модуль, который, помимо прочего, предоставляет побитовый доступ для чтения к файлам. Файлы могут быть большими (сотни ГБ), поэтому я написал простой класс, который позволяет мне обрабатывать файл как строку и скрывает все операции поиска и чтения.
В то время, когда я писал свой класс-обертку, я не знал о модуле mmap. Читая документацию по mmap, я подумал: "Отлично - это как раз то, что мне нужно, я возьму свой код и заменю его на mmap. Возможно, он гораздо эффективнее, и всегда полезно удалять код".
Проблема в том, что mmap не работает с большими файлами! Это очень удивительно для меня, так как я думал, что это, пожалуй, самое очевидное приложение. Если размер файла превышает несколько гигабайт, я получаю EnvironmentError: [Errno 12] Cannot allocate memory
, Это происходит только с 32-битной сборкой Python, поэтому кажется, что она исчерпывает адресное пространство, но я не могу найти никакой документации по этому вопросу.
Мой код просто
f = open('somelargefile', 'rb')
map = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
Итак, мой вопрос: я что-то упускаю здесь очевидное? Есть ли способ заставить mmap работать переносимо с большими файлами, или я должен вернуться к моей простой файловой оболочке?
Обновление: Кажется, что такое чувство, что у mmap Python должны быть те же ограничения, что и у mps POSIX. Чтобы лучше выразить свое разочарование, приведу простой класс, который имеет небольшую часть функциональности mmap.
import os
class Mmap(object):
def __init__(self, f):
"""Initialise with a file object."""
self.source = f
def __getitem__(self, key):
try:
# A slice
self.source.seek(key.start, os.SEEK_SET)
return self.source.read(key.stop - key.start)
except AttributeError:
# single element
self.source.seek(key, os.SEEK_SET)
return self.source.read(1)
Он доступен только для чтения и не делает ничего особенного, но я могу сделать это так же, как с mmap:
map2 = Mmap(f)
print map2[0:10]
print map2[10000000000:10000000010]
кроме того, что нет никаких ограничений на размер файла. Не слишком сложно на самом деле...
8 ответов
От IEEE 1003.1:
Функция mmap() должна установить соответствие между адресным пространством процесса и файлом, объектом общей памяти или типизированным объектом памяти [TYM].
Ему нужно все виртуальное адресное пространство, потому что это именно то, что mmap()
делает
Тот факт, что на самом деле не хватает памяти, не имеет значения - вы не можете отобразить больше адресного пространства, чем у вас есть. Поскольку вы затем берете результат и получаете доступ к нему, как к памяти, как именно вы предлагаете получить доступ к файлу более 2^32 байт? Даже если mmap()
не удалось, вы все равно могли прочитать только первые 4 ГБ, прежде чем исчерпали пространство в 32-битном адресном пространстве. Вы можете, конечно, mmap()
скользящее 32-битное окно над файлом, но это не обязательно принесет вам какую-либо выгоду, если вы не сможете оптимизировать свой шаблон доступа так, чтобы ограничить количество посещений предыдущих окон.
Извините, что отвечаю на мой собственный вопрос, но я думаю, что реальная проблема, с которой я столкнулся, заключалась не в том, чтобы понять, что mmap - это стандартный системный вызов POSIX с особыми характеристиками и ограничениями, и что Python mmap должен просто демонстрировать свою функциональность.
Документация Python не упоминает POSIX mmap, и поэтому, если вы придете к нему как программист Python без особых знаний о POSIX (как я это сделал), тогда проблема с адресным пространством кажется совершенно произвольной и плохо спроектированной!
Спасибо другим авторам за то, что они научили меня истинному значению mmap. К сожалению, никто не предложил лучшую альтернативу моему классу, созданному вручную, для обработки больших файлов как строк, поэтому мне придется придерживаться этого пока. Возможно, я уберу это и сделаю его частью открытого интерфейса моего модуля, когда у меня будет такая возможность.
32-разрядная программа и операционная система могут адресовать максимум 32 бита памяти, т.е. 4 ГБ. Есть и другие факторы, которые делают общую сумму еще меньше; например, Windows резервирует от 0,5 до 2 ГБ для аппаратного доступа, и, конечно, ваша программа также займет некоторое место.
Редактировать: очевидная вещь, которую вы упускаете, это понимание механики mmap в любой операционной системе. Это позволяет вам сопоставить часть файла с диапазоном памяти - как только вы это сделаете, любой доступ к этой части файла происходит с наименьшими возможными издержками. Это снижает накладные расходы, потому что отображение выполняется один раз, и не нужно менять каждый раз, когда вы получаете доступ к другому диапазону. Недостатком является то, что вам нужен открытый диапазон адресов, достаточный для части, которую вы пытаетесь отобразить. Если вы отображаете весь файл за один раз, вам понадобится отверстие на карте памяти, достаточно большое, чтобы вместить весь файл. Если такой дыры не существует или она больше, чем все ваше адресное пространство, она завершится ошибкой.
Модуль mmap предоставляет все инструменты, которые вам нужны для работы с большим файлом, но из-за ограничений, о которых упоминали другие люди, вы не можете отобразить все сразу. Вы можете отобразить блок хорошего размера сразу, выполнить некоторую обработку, а затем удалить это и сопоставить другой. ключевые аргументы mmap
класс length
а также offset
, которые делают именно то, что они звучат, что позволяет вам на карту length
байты, начиная с байта offset
в сопоставленном файле. Каждый раз, когда вы хотите прочитать раздел памяти, который находится за пределами отображенного окна, вы должны отобразить в новом окне.
Вам не хватает того, что mmap - это функция отображения памяти, которая отображает файл в память для произвольного доступа через запрошенный диапазон данных любым способом.
То, что вы ищете, звучит больше как своего рода класс окна данных, который представляет API, позволяющий вам в любое время просматривать маленькие окна большой структуры данных. Доступ за пределы этого окна был бы невозможен, кроме как через вызов собственного API окна данных.
Это хорошо, но это не карта памяти, это то, что предлагает преимущество более широкого диапазона данных за счет более ограничительного API.
Используйте 64-разрядный компьютер с 64-разрядной ОС и 64-разрядной реализацией Python или избегайте memmap()
memmap()
требует поддержки аппаратного обеспечения процессора, чтобы иметь смысл с большими файлами, большими чем несколько ГиБ
Он использует MMU процессора и подсистемы прерываний, чтобы позволить выставлять данные, как если бы они были уже загружены.
MMU - это аппаратное обеспечение, которое будет генерировать прерывание всякий раз, когда осуществляется доступ к адресу, соответствующему данным, не относящимся к физической ОЗУ, и ОС будет обрабатывать прерывание таким образом, который имеет смысл во время выполнения, поэтому код доступа никогда не знает (или должен знать) что данные не помещаются в ОЗУ.
Это делает ваш код доступа простым для написания. Тем не менее, чтобы использовать memmap()
таким образом, все задействованное должно обрабатывать 64-битные адреса.
Или же может быть предпочтительнее избежать memmap()
в целом и сделать свое собственное управление памятью.
Вы устанавливаете параметр длины в ноль, что означает карту во всем файле. В 32-битной сборке это будет невозможно, если длина файла превышает 2 ГБ (возможно, 4 ГБ).
Вы просите ОС отобразить весь файл в диапазоне памяти. Он не будет прочитан до тех пор, пока вы не вызовете сбои страниц при чтении / записи, но все равно необходимо убедиться, что весь диапазон доступен для вашего процесса, и если этот диапазон слишком велик, возникнут трудности.