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

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

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

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


Обновление. Кажется, существует ощущение, что 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]

за исключением того, что нет ограничений на размер файла. Не очень сложно на самом деле...

Ответ 1

Из IEEE 1003.1:

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

Ему нужно все виртуальное адресное пространство, потому что именно это делает mmap().

Тот факт, что на самом деле не хватает памяти, не имеет значения - вы не можете сопоставить больше адресного пространства, чем у вас есть. Поскольку вы затем принимаете результат и получаете доступ, как если бы это была память, как именно вы предлагаете получить более 2 ^ 32 байта в файл? Даже если mmap() не сбой, вы все равно можете прочитать только первые 4 ГБ, прежде чем закончите пространство в 32-разрядном адресном пространстве. Вы можете, конечно, mmap() скользящее 32-битное окно над файлом, но это не обязательно принесет вам какую-либо выгоду, если вы не сможете оптимизировать свой шаблон доступа, чтобы ограничить количество раз, когда вы посещаете предыдущие окна.

Ответ 2

Извините, что ответил на мой собственный вопрос, но я думаю, что реальная проблема, с которой я столкнулась, не понимала, что mmap является стандартным системным вызовом POSIX с особыми характеристиками и ограничениями и что Python mmap предназначен только для раскрытия его функциональности.

Документация Python не упоминает POSIX mmap, и поэтому, если вы приходите к ней как программист на Python, не зная о POSIX (как и я), проблема с адресным пространством кажется довольно произвольной и плохо спроектированной!

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

Ответ 3

32-разрядная программа и операционная система могут адресовать максимум 32 бит памяти, то есть 4 ГБ. Существуют и другие факторы, которые делают общее количество еще меньшим; например, Windows резервирует от 0,5 до 2 ГБ для доступа к аппаратным средствам, и, конечно, ваша программа также займет некоторое пространство.

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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

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

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

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

Ответ 8

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