Почему mmap Python не работает с большими файлами?


[правка: эта проблема относится только к 32-разрядным системам. Если ваш компьютер, ваша ОС и ваша реализация python являются 64-битными, то mmap-ing огромных файлов работает надежно и чрезвычайно эффективно.]

Я пишу модуль, который, помимо всего прочего, позволяет осуществлять побитовый доступ к файлам для чтения. Файлы потенциально могут быть большими (сотни ГБ), поэтому я написал простой класс, который позволяет мне обрабатывать файл как строку и скрывает все поиски и чтение.

В то время, когда я писал свою класс-оболочка я не знал о модуле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 работать переносимо с большими файлами или мне следует вернуться к моей наивной оболочке файлов?


Update: похоже, есть ощущение, что Python mmap должен иметь те же ограничения, что и POSIX mmap. Чтобы лучше выразить мое разочарование здесь-простой класс, который имеет небольшую часть функциональности 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 43

8 ответов:

Из IEEE 1003.1:

Функция mmap() устанавливает сопоставление между адресом процесса пространство и файл, общая память объект, или [Тим] типизированная память объект.

Ему нужно все виртуальное адресное пространство, потому что именно это 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 ГБ для аппаратного доступа, и, конечно, ваша программа также займет некоторое пространство.

Edit: Очевидно, что вам не хватает понимания механики mmap в любой операционной системе. Он позволяет вам сопоставить часть файла с диапазоном памяти-один раз вы сделали это, любой доступ к этой части файла происходит с наименьшими затратами. Это низкие накладные расходы, потому что сопоставление выполняется один раз и не должно изменяться каждый раз, когда вы обращаетесь к другому диапазону. Недостатком является то, что вам нужен открытый диапазон адресов, достаточный для той части, которую вы пытаетесь сопоставить. Если вы сопоставляете весь файл сразу, вам понадобится отверстие в карте памяти, достаточно большое, чтобы вместить весь файл. Если такой дыры не существует, или она больше, чем вся ваша адресное пространство, оно не работает.

Модуль mmap предоставляет все инструменты, необходимые для поиска в вашем большом файле, но из-за ограничений, упомянутых другими людьми, вы не можете сопоставить его все сразу. Вы можете сопоставить большой кусок сразу, выполнить некоторую обработку, а затем размонтировать его и сопоставить другой. ключевыми аргументами для класса mmap являются length и offset, которые делают именно то, что они звучат, позволяя сопоставлять length байты, начиная с байта offset в сопоставленном файле. В любое время, когда вы захотите прочитать раздел памяти, который находится за пределами отображенного окна, вы должны отобразить в новом окне.

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

То, что вы ищете, больше похоже на какой-то класс окна данных, который представляет api, позволяющий вам смотреть на маленькие окна большой структуры данных в любое время. Доступ за пределы этого окна будет возможен только при вызове собственного api окна данных.

Это прекрасно, но это не карта памяти, это то, что предлагает преимущество более широкого диапазона данных за счет более ограниченного api.

Вы устанавливаете параметр length равным нулю,что означает отображение во всем файле. При 32-битной сборке это невозможно, если длина файла превышает 2 ГБ (возможно, 4 ГБ).

Используйте 64-разрядный компьютер с 64-разрядной ОС и 64-разрядной реализацией python или избегайте memmap()

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

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

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

Это упрощает написание кода доступа. Однако, чтобы использовать memmap() таким образом, все, что связано с этим, должно будет обрабатывать 64-битные адреса.

Или же предпочтительнее вообще избегать memmap() и заниматься собственным управлением памятью.

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