2016-06-14 4 views
5

редактировать: Использование Win10 и питона 3,5Python, почему mmap.move() заполняет память?

У меня есть функция, которая использует ММАП удалить байты из файла в определенном смещении:

def delete_bytes(fobj, offset, size): 
    fobj.seek(0, 2) 
    filesize = fobj.tell() 
    move_size = filesize - offset - size 

    fobj.flush() 
    file_map = mmap.mmap(fobj.fileno(), filesize) 
    file_map.move(offset, offset + size, move_size) 
    file_map.close() 

    fobj.truncate(filesize - size) 
    fobj.flush() 

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

После некоторых экспериментов я обнаружил, что метод move() был виновником здесь и, в частности, количеством перемещаемых данных (move_size). Объем используемой памяти эквивалентен общему количеству перемещаемых данных mmap.move(). Если у меня есть 100 файлов с перемещением ~ 30 МБ, память заполняется ~ 3 ГБ.

Почему данные не перемещаются из памяти?

Вещи я пытался, которые не имели никакого эффекта:

  • призывающие gc.collect() в конце функции.
  • переписывание функции для перемещения в небольших кусках.
+0

В какой операционной системе вы используете? Версия Python. – wind85

+0

Можете ли вы также проверить, используется ли память вашим процессом python или операционной системой? – Leon

+0

Извините, забыли упомянуть: я на Win10 и python 3.5. Как проверить, используется ли память для python или ОС? – mahkitah

ответ

1

Это похоже должно работа. Я нашел один подозрительный бит в исходном коде mmapmodule.c, #ifdef MS_WINDOWS. В частности, после того, как все настройки для разбора аргументов, то код, то делает это:

if (fileno != -1 && fileno != 0) { 
    /* Ensure that fileno is within the CRT's valid range */ 
    if (_PyVerify_fd(fileno) == 0) { 
     PyErr_SetFromErrno(PyExc_OSError); 
     return NULL; 
    } 
    fh = (HANDLE)_get_osfhandle(fileno); 
    if (fh==(HANDLE)-1) { 
     PyErr_SetFromErrno(PyExc_OSError); 
     return NULL; 
    } 
    /* Win9x appears to need us seeked to zero */ 
    lseek(fileno, 0, SEEK_SET); 
} 

который перемещает ваш основной объект файла смещен от «конца файла» в «начало файла», а затем оставляет его там. Кажется, что это должно ничего не сломать, но, возможно, стоит сделать свой собственный поиск в начале файла непосредственно перед вызовом mmap.mmap для сопоставления файла.

(Все ниже неправильно, но остается в так как есть комментарии к нему.)


В общем, после того, как с помощью mmap(), вы должны использовать munmap(), чтобы отменить отображение. Простое закрытие дескриптора файла не имеет никакого эффекта. В Linux documentation называет это в явном виде:

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

(документация BSD похожа. Windows, может вести себя по-разному от Unix-подобных систем, но на то, что вы видите, говорит о том, что они работают точно так же.)

К сожалению, модуль ММАП Пайтона не связывает системный вызов munmap (а не mprotect), по крайней мере, как 2.7.11, так и 3.4.4. В качестве обходного пути вы можете использовать модуль ctypes.См. Пример this question (он вызывает reboot, но тот же метод работает для всех функций библиотеки C). Или, для более приятного метода, вы можете написать обертки в .

+0

Не 'mmap.close()' выполняет 'unmap()' под? – Leon

+1

'mmap.close()' выполняет вызов 'UnmapViewOfFile' (windows) или' munmap' (unix) (python 3.4, mmapmodule.c). –

+0

Само отображение не является проблемой. Если я удаляю строку с помощью 'mmap.move()' или заменяю ее другим методом (например, 'mmap.resize()'), проблем нет. – mahkitah