2009-12-29 18 views
13

Ho alcuni file enormi che devo analizzare e le persone raccomandano mmap perché questo dovrebbe evitare di dover allocare l'intero file in memoria.problema mmap, alloca enormi quantità di memoria

Ma guardando "in alto" sembra che sto aprendo l'intero file nella memoria, quindi penso che devo fare qualcosa di sbagliato. 'spettacoli in alto> 2.1 gig'

Questo è uno snippet di codice che mostra quello che sto facendo.

Grazie

#include <stdio.h> 
#include <stdlib.h> 
#include <err.h> 
#include <fcntl.h> 
#include <sysexits.h> 
#include <unistd.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <sys/mman.h> 
#include <cstring> 
int main (int argc, char *argv[]) { 
    struct stat sb; 
    char *p,*q; 
    //open filedescriptor 
    int fd = open (argv[1], O_RDONLY); 
    //initialize a stat for getting the filesize 
    if (fstat (fd, &sb) == -1) { 
    perror ("fstat"); 
    return 1; 
    } 
    //do the actual mmap, and keep pointer to the first element 
    p =(char *) mmap (0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0); 
    q=p; 
    //something went wrong 
    if (p == MAP_FAILED) { 
    perror ("mmap"); 
    return 1; 
    } 
    //lets just count the number of lines 
    size_t numlines=0; 
    while(*p++!='\0') 
    if(*p=='\n') 
     numlines++; 
    fprintf(stderr,"numlines:%lu\n",numlines); 
    //unmap it 
    if (munmap (q, sb.st_size) == -1) { 
    perror ("munmap"); 
    return 1; 
    } 
    if (close (fd) == -1) { 
    perror ("close"); 
    return 1; 
    } 
    return 0; 
} 
+0

@monkeyking, la chiusura corretta per code-pre è/pre/code, non post :-) Risolto il problema con i tag del codice. – paxdiablo

+0

Ahh grazie un milione! Che dire di #include Non ho potuto inserire questi nel codice di esempio – monkeyking

+0

Segna l'intero blocco quindi usa CTRL-K - questo farà rientrare di quattro spazi. L'ho fatto ora e dovresti essere in grado di vedere una stdio include. – paxdiablo

risposta

39

No, quello che stai facendo è mappatura il file nella memoria. Questo è diverso dalla lettura effettiva del file in memoria.

Se lo leggete, dovreste trasferire l'intero contenuto in memoria. Mappandolo, si lascia che il sistema operativo lo gestisca. Se si tenta di leggere o scrivere in una posizione in quell'area di memoria, il sistema operativo caricherà per prima la sezione pertinente. Sarà non caricare l'intero file a meno che non sia necessario l'intero file.

Ecco dove ottieni il tuo guadagno in termini di prestazioni. Se si esegue la mappatura dell'intero file, ma si cambia solo un byte e lo si deseleziona, si scoprirà che non ci sono molti I/O su disco.

Ovviamente, se si tocca ogni byte nel file, allora sì, verrà caricato in un punto qualsiasi ma non necessariamente nella RAM fisica tutto in una volta. Ma questo è il caso anche se si carica l'intero file in anticipo. Il sistema operativo sostituirà parte dei dati se non c'è abbastanza memoria fisica per contenerli tutti insieme a quelli degli altri processi nel sistema.

I principali vantaggi della mappatura della memoria sono:

  • si rinviare la lettura delle sezioni di file fino a quando non sono necessari (e, se sono mai necessari, che non vengano caricati). Quindi non c'è un grosso costo iniziale quando si carica l'intero file. Ammortizza il costo del caricamento.
  • Le scritture sono automatizzate, non è necessario scrivere ogni byte. Basta chiuderlo e il sistema operativo scriverà le sezioni modificate. Penso che questo accada anche quando la memoria viene scambiata (in situazioni di poca memoria fisica), poiché il buffer è semplicemente una finestra sul file.

Ricordare che è molto probabile una disconnessione tra l'utilizzo dello spazio indirizzo e l'utilizzo della memoria fisica. È possibile allocare uno spazio di indirizzamento di 4G (idealmente, anche se potrebbero esserci limiti di OS, BIOS o hardware) in una macchina a 32 bit con solo 1G di RAM. Il sistema operativo gestisce il paging da e verso il disco.

E per rispondere alla vostra ulteriore richiesta di chiarimento:

tanto per chiarire. Quindi, se ho bisogno dell'intero file, mmap caricherà effettivamente l'intero file?

Sì, ma potrebbero non essere in memoria fisica tutti in una volta. Il sistema operativo scambierà i bit al filesystem per introdurre nuovi bit.

Ma lo farà anche se avete letto l'intero file manualmente. La differenza tra queste due situazioni è la seguente.

Con il file letto in memoria manualmente, il sistema operativo scambierà parti del proprio spazio indirizzo (potrebbero includere i dati o potrebbero non esserlo) nel file di scambio. E avrai bisogno di riscrivere manualmente il file quando hai finito con esso.

Con la mappatura della memoria, è stato effettivamente detto di utilizzare il file originale come area di scambio aggiuntiva per quel file/solo memoria. E, quando i dati vengono scritti in nell'area di scambio, influisce immediatamente sul file effettivo. Quindi non è necessario dover riscrivere manualmente nulla quando hai finito e senza modificare il normale swap (di solito).

E 'davvero solo una finestra per il file:

                                        memory mapped file image

+0

Giusto per chiarire. Quindi, se ho bisogno dell'intero file, mmap caricherà effettivamente l'intero file? – monkeyking

+0

Sì, consultare l'aggiornamento. – paxdiablo

+0

@paxdiablo, potresti anche chiarire questo: "Con il file letto in memoria manualmente, il sistema operativo scambierà parti del tuo spazio indirizzo (potrebbe includere i dati o meno) nel file di scambio". Intendevi che se leggiamo (2) l'intero file in memoria -> scrivi (2) alcuni dati -> chiudi (2) esso (fsync (2) se necessario) il file non conterrà le ultime modifiche ? Oppure deve essere utilizzato lo schema seguente? leggi (2) -> alcune modifiche -> scrivi (2) l'intero file. – dshil

0

Il sistema cercherà sicuramente di inserire tutti i dati nella memoria fisica. Quello che conserverai è swap.

+0

sbagliato. la VM utilizzerà la RAM per rendere disponibile il file; ma sarà scambiato non appena ci sarà un po 'di pressione della memoria. È quasi esattamente come usare la RAM come cache per il file. – Javier

+0

Sbagliato. Non utilizzerà mai lo spazio di swap per una mappatura di sola lettura. Farà I/O per scambiarlo, ma non userete spazio. – bmargulies

3

top ha molte colonne relative alla memoria. Molti di questi sono basati sulla dimensione dello spazio di memoria mappato sul processo; incluse eventuali librerie condivise, RAM scambiata e spazio mmapped.

Controllare la colonna RES, questo è correlato alla RAM fisica attualmente in uso. Penso (ma non sono sicuro) che includerebbe la RAM usata per "memorizzare" il file mmap'ped

1

"allocare l'intero file in memoria" confonde due problemi. Uno è la quantità di memoria virtuale allocata; l'altro è quali parti del file vengono letti dal disco in memoria. Qui stai allocando abbastanza spazio per contenere l'intero file. Tuttavia, solo le pagine che si toccano verranno effettivamente modificate sul disco. E, saranno modificati correttamente, non importa cosa succede con il processo, una volta aggiornati i byte nella memoria che mmap ha assegnato per te. È possibile allocare meno memoria mappando solo una sezione del file alla volta utilizzando i parametri "size" e "offset" di mmap. Quindi devi gestire una finestra nel file da solo mappando e annullando la mappatura, magari spostando la finestra attraverso il file. L'allocazione di una grande porzione di memoria richiede tempo apprezzabile. Questo può introdurre un ritardo imprevisto nell'applicazione. Se il tuo processo richiede già molta memoria, la memoria virtuale potrebbe essere frammentata e potrebbe essere impossibile trovare un chunk abbastanza grande per un file di grandi dimensioni nel momento in cui lo chiedi. Potrebbe quindi essere necessario provare a eseguire la mappatura il prima possibile o utilizzare una strategia per tenere a disposizione una porzione abbastanza grande di memoria finché non ne avrai bisogno.

Tuttavia, visto che si specifica che è necessario analizzare il file, perché non evitarlo completamente organizzando il parser per operare su un flusso di dati? Quindi la maggior parte di cui avrai bisogno è un po 'di look-ahead e un po' di storia, invece di dover mappare i blocchi discreti del file in memoria.

2

Potrebbe essere stato offerto il consiglio sbagliato.

I file mappati in memoria (mmap) utilizzeranno sempre più memoria mentre vengono analizzati. Quando la memoria fisica diventa bassa, il kernel rimuoverà le sezioni del file dalla memoria fisica in base al suo algoritmo LRU (usato meno di recente). Ma l'LRU è anche globale.L'LRU può anche forzare altri processi a scambiare pagine su disco e ridurre la cache del disco. Ciò può influire negativamente negativamente sulle prestazioni di altri processi e del sistema nel suo complesso.

Se si sta leggendo linearmente i file, come il conteggio del numero di righe, mmap è una scelta sbagliata, poiché riempirà la memoria fisica prima di restituire la memoria al sistema. Sarebbe preferibile utilizzare i metodi di I/O tradizionali che trasmettono in streaming o letti in un blocco alla volta. In questo modo la memoria può essere rilasciata immediatamente dopo.

Se si accede in modo casuale a un file, mmap è una scelta accettabile. Ma non è ottimale dal momento che si sta ancora facendo affidamento sull'algoritmo LRU generale del kernel, ma è più veloce da utilizzare rispetto alla scrittura del meccanismo di memorizzazione nella cache.

In generale, non consiglierei mai a nessuno di usare mmap, tranne che per alcuni casi estremi di prestazioni edge - come l'accesso al file da più processi o thread allo stesso tempo, o quando il file è piccolo in relazione alla quantità di libero memoria disponibile.

+1

Meh. Puoi fare circa 10 ricerche di alberi usando mmap nel tempo necessario per fissare una struttura ad albero B + per blocco. –

+0

Non necessariamente vero. Le prestazioni del primo IO letto saranno quasi identiche (per tutti gli scopi pratici) tra mmap e pread - entrambi devono leggerlo dai media. Il problema è con le letture successive. Mmap userà l'algoritmo LRU di sfratto della memoria del kernel per decidere quali pagine mappare. Con Pread il sottosistema IO deciderà quali blocchi rimuovere dalla cache (se ce ne sono). Nessuno dei due approcci è altamente efficiente in termini di rilascio di risorse di memoria inutilizzate. Pertanto, l'applicazione basata su mmap può ridurre le prestazioni e l'efficienza dell'intero sistema danneggiando le risorse di memoria. – tgiphil

+1

Non si contano le migliaia di cicli CPU sprecati per ogni chiamata di sistema. mmap è più veloce. –

0

È necessario specificare una dimensione inferiore alla dimensione totale del file nella chiamata mmap, se non si desidera l'intero file mappato in memoria in una volta. Usando il parametro offset e una dimensione più piccola, puoi mappare in "windows" il file più grande, un pezzo alla volta.

Se l'analisi è un singolo passaggio del file, con un lookback o un lookback minimo, in realtà non si guadagna nulla utilizzando mmap anziché l'I/O bufferizzato della libreria standard. Nell'esempio che hai dato di contare le nuove righe nel file, sarebbe altrettanto veloce farlo con fread(). Suppongo che il tuo parsing attuale sia più complesso, comunque.

Se è necessario leggere più di una parte del file alla volta, sarà necessario gestire più regioni mmap, che possono essere rapidamente complicate.

0

Un po 'fuori tema.

Non sono del tutto d'accordo con la risposta di Mark. In realtà mmap è più veloce di fread.

Nonostante abbia sfruttato il buffer del sistema, fread dispone anche di un buffer interno e inoltre i dati verranno copiati nel buffer fornito dall'utente come viene chiamato.

Al contrario, mmap è sufficiente restituire un puntatore al buffer del sistema. Quindi c'è un a due copie di memoria che salva.

Ma usare mmap un po 'pericoloso. È necessario assicurarsi che il puntatore non esca mai dal file o che ci sia un errore di segmento . Mentre in questo caso fread solo restituisce zero.

+0

In realtà ho fatto benchmarking che mostra che (su Mac OS X, comunque) non c'è quasi nessuna differenza di throughput tra mmap con finestra e fread per la lettura diretta. Sì, utilizzando la libreria di alto livello, i dati vengono copiati (fino a tre volte), ma il tempo di copiare i dati è trascurabile rispetto al tempo di I/O effettivo. Di solito uso l'interfaccia di più alto livello che è appropriata. –

+1

@Mark: concorda con te quando il file viene letto la prima volta. Tuttavia, se il programma legge il file più di una volta, o il programma viene eseguito ripetutamente (ad esempio il server Web), ci sarà un'enorme differenza. (La modifica di 'fread' in' mmap' ha reso l'intero programma più veloce del 50% in una delle mie esperienze) – iamamac

+0

Soprattutto se si considera che 'fseek' +' fread' legge sempre la dimensione completa del buffer per qualsiasi dimensione indicata. –

4

È inoltre possibile utilizzare fadvise (2) (e madvise (2), vedere anche posix_fadvise & posix_madvise) per contrassegnare il file mmaped (o le sue parti) come read-once.

#include <sys/mman.h> 

int madvise(void *start, size_t length, int advice); 

Il consiglio è indicato nel parametro consiglio che può essere

MADV_SEQUENTIAL 

Aspettatevi riferimenti alle pagine in ordine sequenziale. (Quindi, le pagine nell'intervallo specificato possono essere letti in modo aggressivo, e possono essere liberate subito dopo l'accesso.)

Portabilità: posix_madvise e posix_fadvise fa parte opzione avanzata REALTIME di IEEE Std 1003.1 2004. E costanti sarà POSIX_MADV_SEQUENTIAL e POSIX_FADV_SEQUENTIAL di.