2016-06-14 19 views
5

edit: Usando Win10 e pitone 3.5Python, perché mmap.move() riempie la memoria?

Ho una funzione che usa mmap per rimuovere i byte da un file ad un certo offset:

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

E funziona super veloce, ma quando l'eseguo su un un numero elevato di file, la memoria si riempie rapidamente e il mio sistema non risponde.

Dopo alcuni esperimenti, ho scoperto che il metodo move() era il colpevole qui, e in particolare la quantità di dati spostati (move_size). La quantità di memoria utilizzata è equivalente alla quantità totale di dati spostati da mmap.move(). Se ho 100 file con ogni ~ 30 MB spostati, la memoria viene riempita con ~ 3 GB.

Perché i dati spostati non vengono rilasciati dalla memoria?

cose che ho provato che non aveva alcun effetto:

  • chiamando gc.collect() al termine della funzione.
  • riscrivere la funzione per spostarsi in piccoli blocchi.
+0

Quale sistema operativo utilizzate? Anche la versione Python. – wind85

+0

Puoi verificare anche se la memoria è utilizzata dal tuo processo Python o dal sistema operativo? – Leon

+0

Scusa, ho dimenticato di menzionare: Sono su Win10 e Python 3.5. Come posso controllare se la memoria è usata da Python o dal SO? – mahkitah

risposta

1

Questo sembra che sia lavoro. Ho trovato un bit sospetto nel codice sorgente mmapmodule.c, #ifdef MS_WINDOWS. In particolare, dopo tutto la messa a punto per analizzare argomenti, il codice fa allora questo:

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

che si sposta su "all'inizio del file" e poi se ne va di compensato da "fine del file" l'oggetto file sottostante lì. Sembra che sia non dover rompere nulla, ma potrebbe valere la pena di fare il proprio seek-to-start-of-file appena prima di chiamare mmap.mmap per mappare il file.

(Tutto sotto è sbagliato, ma ha lasciato in quanto ci sono commenti su di esso.)


In generale, dopo aver usato mmap(), è necessario utilizzare munmap() per annullare la mappatura. La semplice chiusura del descrittore di file non ha alcun effetto. I Linux documentation chiama questo esplicitamente:

munmap()
La chiamata di sistema munmap() cancella le mappature per l'intervallo di indirizzi specificato, e provoca ulteriori riferimenti a indirizzi all'interno della gamma di generare i riferimenti di memoria non validi. Anche la regione viene automaticamente non mappata al termine del processo. D'altra parte, la chiusura del descrittore di file non annulla la regione.

(La documentazione BSD è simile. Windows potrebbe comportarsi in modo diverso dai sistemi Unix-like qui, ma ciò che state vedendo suggerisce che essi funzionano allo stesso modo.)

Purtroppo, il modulo mmap di Python non lo fa bind la chiamata di sistema munmap (né mprotect), almeno fino al 2.7.11 e 3.4.4. Per ovviare al problema, è possibile utilizzare il modulo ctypes.Vedere this question per un esempio (chiama reboot ma la stessa tecnica funziona per tutte le funzioni della libreria C). Oppure, per un metodo un po 'più bello, puoi scrivere wrapper in .

+0

Non 'mmap.close()' esegue 'unmap()' sotto? – Leon

+1

'mmap.close()' chiama 'UnmapViewOfFile' (windows) o' munmap' (unix) (python 3.4, mmapmodule.c). –

+0

La mappatura non è un problema. Se rimuovo la riga con 'mmap.move()' o la sostituisco con un altro metodo (come 'mmap.resize()') non ci sono problemi. – mahkitah

Problemi correlati