rendimiento - Python, ¿por qué mmap.move() llena la memoria?


editar: Usando Win10 y python 3.5

Tengo una función que usa mmap para eliminar bytes de un archivo en un desplazamiento determinado:

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()

Funciona súper rápido, pero cuando lo ejecuto en una gran cantidad de archivos, la memoria se llena rápidamente y mi sistema deja de responder.

Después de experimentar un poco, descubrí que el método move() era el culpable aquí y, en particular, la cantidad de datos que se movían (move_size). La cantidad de memoria que se utiliza es equivalente a la total cantidad de datos que mmap.move() mueve. Si tengo 100 archivos con cada movimiento de ~30 MB, la memoria se llena con ~3 GB.

¿Por qué los datos movidos no se liberan de la memoria?

Cosas que probé que no surtieron efecto:

  • llamar a gc.collect() al final de la función.
  • reescribiendo la función para que se mueva en pequeños fragmentos.


------------Respuesta------------

Parece que debería funcionar. Encontré un bit sospechoso en mmapmodule.c scódigo fuente, #ifdef MS_WINDOWS. Específicamente, después de toda la configuración para analizar argumentos, el código hace esto:

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);
}

que mueve el desplazamiento de su objeto de archivo subyacente de "fin de archivo" a "inicio de archivo" y luego lo deja allí. Parece que no debería romper nada, pero podría valer la pena hacer su propia búsqueda de inicio de archivo justo antes de llamar a mmap.mmap para mapear el archivo.

(Todo a continuación está mal, pero se dejó porque hay comentarios al respecto).


En general, después de usar mmap(), debe usar munmap() para deshacer el mapeo. Simplemente cerrar el descriptor de archivo no tiene ningún efecto. La documentación de Linux menciona esto explícitamente:

munmap()
La llamada al sistema munmap() elimina las asignaciones para el rango de direcciones especificado y hace que más referencias a direcciones dentro del rango generen referencias de memoria no válidas. La región también se desasigna automáticamente cuando finaliza el proceso. Por otra parte, cerrar elel descriptor de archivo no desasigna la región.

(La documentación de BSD es similar. Windows puede comportarse de manera diferente a los sistemas similares a Unix aquí, pero lo que está viendo sugiere que funcionan de la misma manera).

Desafortunadamente, el módulo mmap de Python no vincula la llamada al sistema munmap (ni mprotect), al menos a partir de la 2.7.11 y la 3.4.4. Como solución alternativa, puede utilizar el módulo ctypes. Vea esta pregunta para ver un ejemplo (llama a reiniciar pero la misma técnica funciona para todas las bibliotecas Cfunciones aleatorias). O, para un método algo más agradable, puede escribir envoltorios en cython.

Etiquetas: mmap memory performance python

Artículos relacionados:

servicios web: cómo extraer/descifrar el tiempo de caducidad del token web Json emitido externamente

javascript - formulario de botón de radio sin enviar