Как использовать mmap в python, когда весь файл слишком большой

У меня есть python script, который читает файл по строкам и смотрит, соответствует ли каждая строка регулярному выражению.

Я хотел бы улучшить производительность этого script, используя карту памяти для файла перед поиском. Я рассмотрел пример mmap: http://docs.python.org/2/library/mmap.html

Мой вопрос в том, как я могу mmap файл, когда он слишком большой (15 ГБ) для памяти моей машины (4 ГБ)

Я прочитал файл следующим образом:

fi = open(log_file, 'r', buffering=10*1024*1024)

for line in fi: 
    //do somemthong

fi.close()

Так как я устанавливаю буфер на 10 МБ, с точки зрения производительности, то он такой же, как у mmap 10 Мбайт файла?

Спасибо.

Ответ 1

Во-первых, память вашей машины не имеет значения. Это размер вашего процесса адресное пространство, которое имеет значение. С 32-разрядным Python это будет где-то менее 4 ГБ. С 64-битным Python его будет более чем достаточно.

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

Итак, первым ответом является "использование 64-битного Python". Но, очевидно, это может быть неприменимо в вашем случае.

Очевидной альтернативой является карта в первом 1 ГБ, поиск, который, отмените его, карту в следующем 1 ГБ и т.д. Как вы это делаете, указав параметры length и offset на mmap метод. Например:

m = mmap.mmap(f.fileno(), length=1024*1024*1024, offset=1536*1024*1024)

Однако регулярное выражение, которое вы ищете, можно найти на полпути в первом 1 ГБ, а второе - во втором. Итак, вам нужно использовать оконную карту в первом 1 ГБ, искать, развязать, а затем отображать частично перекрывающиеся 1 ГБ и т.д.

Вопрос в том, сколько перекрытий вам нужно? Если вы знаете максимально возможный размер матча, вам не нужно ничего больше. И если вы не знаете... ну, тогда нет возможности реально решить проблему, не разбивая ваше регулярное выражение, если это не очевидно, представьте, как вы могли бы найти совпадение 2 ГБ в одном окне на 1 ГБ.

Отвечая на следующий вопрос:

Так как я устанавливаю буфер на 10 МБ, с точки зрения производительности, то он такой же, как у mmap 10 Мбайт файла?

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

Если вы хотите, чтобы я догадался: я думаю, что mmap может быть быстрее здесь, но только потому, что (как подразумевал JF Себастьян), цикл и вызов re.match 128K раз, как часто может привести к тому, что ваш код будет привязан к процессору вместо IO переплет. Но вы можете оптимизировать это без mmap, просто используя read. Таким образом, mmap будет быстрее, чем read? Учитывая размеры, я ожидал бы, что производительность mmap будет намного быстрее на старых платформах Unix, примерно на современных Unix-платформах, и немного медленнее в Windows. (Вы можете получить большие преимущества производительности из mmap более read или read + lseek, если используете madvise, но это не имеет значения здесь.) Но на самом деле это просто предположение.

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

Ответ 2

Я пришел, чтобы попробовать использовать mmap, потому что я использовал fileh.readline() в файле размером в десятки ГБ и хотел сделать его быстрее. Утилита Unix strace, похоже, показывает, что файл теперь читается в блоках 4kB, и, по крайней мере, вывод из strace кажется мне напечатанным медленно, и я знаю, что разбор файла занимает много часов.

$ strace -v -f -p 32495
Process 32495 attached
read(5, "blah blah blah foo bar xxxxxxxxx"..., 4096) = 4096
read(5, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 4096) = 4096
read(5, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 4096) = 4096
read(5, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 4096) = 4096
^CProcess 32495 detached
$

Этот поток пока единственный, объясняющий меня, я не должен пытаться mmap слишком большого файла. Я не понимаю, почему у него нет вспомогательной функции типа mmap_for_dummies(filename), которая будет делать внутри os.path.size(имя файла), а затем либо делать нормальный open(filename, 'r', buffering=10*1024*1024), либо делать mmap.mmap(open(filename).fileno()). Я, конечно, хочу избегать возиться с скользящим окном, но функция будет делать простое решение о том, делать ли mmap или нет, было бы достаточно для меня.

Наконец, мне все еще не ясно, почему некоторые примеры в Интернете упоминают open(filename, 'rb') без объяснения причин (например, https://docs.python.org/2/library/mmap.html). Если вы часто хотите использовать файл в цикле for с вызовом .readline(), я не знаю, должен ли я открываться в режиме 'rb' или просто 'r' (я думаю, что необходимо сохранить '\n').

Спасибо за упоминание аргумента buffering=10*1024*1024), вероятно, более полезно, чем изменение моего кода, чтобы получить некоторую скорость.