Python,为什么mmap.move()会填满内存?

mah*_*tah 6 python memory performance mmap

编辑:使用Win10和python 3.5

我有一个函数,它使用mmap从某个偏移量的文件中删除字节:

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()
Run Code Online (Sandbox Code Playgroud)

它运行速度非常快,但是当我在大量文件上运行它时,内存很快就会填满,我的系统也没有响应.

经过一些实验,我发现move()方法是这里的罪魁祸首,特别是移动的数据量(move_size).正在使用的内存量等于要移动的数据总量mmap.move().如果我有100个文件,每个移动约30 MB,内存充满〜3GB.

为什么移动的数据不是从内存中释放出来的?

我试过的事情没有效果:

  • 调用gc.collect()的函数结束.
  • 重写函数以小块移动.

tor*_*rek 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);
}
Run Code Online (Sandbox Code Playgroud)

它将底层文件对象的偏移量从“文件结尾”移动到“文件开头”,然后将其留在那里。这似乎不应该破坏任何东西,但在调用映射文件之前,可能值得自己进行文件开头查找mmap.mmap

(以下所有内容都是错误的,但由于有评论而保留。)


一般情况下,使用 后mmap(),必须使用munmap()来撤消映射。简单地关闭文件描述符是没有效果的。Linux 文档明确指出了这一点:

munmap()
munmap()系统调用删除指定地址范围的映射,并导致进一步引用该范围内的地址以生成无效的内存引用。当进程终止时,该区域也会自动取消映射。另一方面,关闭文件描述符不会取消该区域的映射。

(BSD 文档是类似的。Windows 的行为可能与此处的类 Unix 系统不同,但您所看到的表明它们的工作方式相同。)

不幸的是,Python 的 mmap 模块不绑定munmap系统调用(也不绑定mprotect),至少从 2.7.11 和 3.4.4 开始是这样。作为解决方法,您可以使用该ctypes模块。请参阅此问题的示例(它调用reboot但相同的技术适用于所有 C 库函数)。或者,对于更好的方法,您可以在中编写包装器。