mmap () vs. blocchi di lettura


185

Sto lavorando a un programma che elaborerà file che potrebbero avere dimensioni pari o superiori a 100 GB. I file contengono set di record a lunghezza variabile. Ho una prima implementazione attiva e sto cercando di migliorare le prestazioni, in particolare per eseguire l'I / O in modo più efficiente poiché il file di input viene scansionato molte volte.

Esiste una regola empirica per l'utilizzo mmap()rispetto alla lettura in blocchi tramite la libreria di C ++ fstream? Quello che mi piacerebbe fare è leggere blocchi di grandi dimensioni dal disco in un buffer, elaborare i record completi dal buffer e quindi leggere di più.

Il mmap()codice potrebbe potenzialmente diventare molto disordinato poiché mmapi blocchi devono trovarsi su confini di dimensioni di pagina (la mia comprensione) e i record potrebbero potenzialmente piacere oltre i confini di pagina. Con fstreams, posso solo cercare l'inizio di un record e ricominciare a leggere, dal momento che non siamo limitati a leggere blocchi che giacciono su confini di dimensioni di pagina.

Come posso decidere tra queste due opzioni senza prima scrivere una completa implementazione? Qualche regola empirica (ad esempio, mmap()è 2 volte più veloce) o semplici test?


1
Questa è una lettura interessante: medium.com/@sasha_f/… Negli esperimenti mmap()è 2-6 volte più veloce rispetto all'utilizzo di syscalls, ad es read().
mplattner

Risposte:


208

Stavo cercando di trovare l'ultima parola sulle prestazioni di mmap / read su Linux e mi sono imbattuto in un bel post ( link ) nella mailing list del kernel Linux. È del 2000, quindi da allora ci sono stati molti miglioramenti all'IO e alla memoria virtuale nel kernel, ma spiega bene il perché mmapo readpotrebbe essere più veloce o più lento.

  • Una chiamata a mmapha un overhead maggiore di read(proprio come epollha un overhead maggiore di poll, che ha un overhead maggiore di read). La modifica dei mapping di memoria virtuale è un'operazione piuttosto costosa su alcuni processori per le stesse ragioni per cui il passaggio tra processi diversi è costoso.
  • Il sistema IO può già utilizzare la cache del disco, quindi se leggi un file, andrai in cache o lo mancherai indipendentemente dal metodo che usi.

Però,

  • Le mappe di memoria sono generalmente più veloci per l'accesso casuale, specialmente se i modelli di accesso sono scarsi e imprevedibili.
  • Le mappe di memoria consentono di continuare a utilizzare le pagine dalla cache fino al termine. Ciò significa che se si utilizza un file pesantemente per un lungo periodo di tempo, quindi si chiude e si riapre, le pagine verranno comunque memorizzate nella cache. Con read, il tuo file potrebbe essere stato scaricato dalla cache anni fa. Questo non si applica se si utilizza un file e lo si scarta immediatamente. (Se provi a mlockpagine solo per tenerle nella cache, stai cercando di superare in astuzia la cache del disco e questo tipo di sciocco raramente aiuta le prestazioni del sistema).
  • La lettura diretta di un file è molto semplice e veloce.

La discussione su mmap / read mi ricorda altre due discussioni sulla performance:

  • Alcuni programmatori Java sono rimasti scioccati nello scoprire che l'I / O senza blocco è spesso più lento del blocco dell'I / O, il che ha perfettamente senso se sai che l'I / O senza blocco richiede di fare più syscall.

  • Alcuni altri programmatori di rete sono rimasti scioccati nell'apprendere che epollspesso è più lento di poll, il che ha perfettamente senso se sai che la gestione epollrichiede di fare più syscall.

Conclusione: utilizzare le mappe di memoria se si accede ai dati in modo casuale, se li si conserva a lungo o se si sa che è possibile condividerli con altri processi ( MAP_SHAREDnon è molto interessante se non esiste una condivisione effettiva). Leggi i file normalmente se accedi ai dati in sequenza o li scarti dopo la lettura. E se uno dei due metodi rende il programma meno complessa, fare quello . Per molti casi del mondo reale non esiste un modo sicuro per dimostrarne uno più veloce senza testare l'applicazione reale e NON un benchmark.

(Mi dispiace per aver annullato questa domanda, ma stavo cercando una risposta e questa domanda continuava a venire in cima ai risultati di Google.)


Tieni presente che l'utilizzo di qualsiasi consiglio basato su hardware e software degli anni 2000, senza testarlo oggi sarebbe un approccio molto sospetto. Inoltre, mentre molti dei fatti su mmapvs read()in quel thread sono ancora veri come in passato, le prestazioni complessive non possono essere determinate sommando i pro e i contro, ma solo testando una particolare configurazione hardware. Ad esempio, è discutibile che "Una chiamata a mmap ha un overhead maggiore di quello letto" - yes mmapdeve aggiungere mappature alla tabella della pagina del processo, ma readdeve copiare tutti i byte letti dal kernel nello spazio utente.
BeeOnRope,

Il risultato è che, sul mio hardware (Intel moderno, circa 2018), mmapha un sovraccarico inferiore rispetto readalle letture di dimensioni superiori alle pagine (4 KiB). Ora è molto vero che se si desidera accedere ai dati in modo sparso e casuale, mmapè davvero, davvero buono - ma il contrario non è necessariamente vero: mmappotrebbe essere comunque il migliore anche per l'accesso sequenziale.
BeeOnRope,

1
@BeeOnRope: potresti essere scettico nei confronti dei consigli basati su hardware e software degli anni 2000, ma sono ancora più scettico sui benchmark che non forniscono una metodologia e dati. Se si desidera presentare un caso mmappiù rapido, mi aspetto di vedere come minimo l'intero apparato di test (codice sorgente) con i risultati tabulati e il numero di modello del processore.
Dietrich Epp,

@BeeOnRope: tieni anche presente che quando stai testando bit del sistema di memoria come questo, i microbenchmark possono essere estremamente ingannevoli perché un flush TLB può influire negativamente sulle prestazioni del resto del tuo programma e questo impatto non si manifesterà se si misura solo lo mmap stesso.
Dietrich Epp,

2
@DietrichEpp - sì, imparerò bene gli effetti TLB. Si noti che mmapnon svuota il TLB se non in circostanze insolite (ma munmappotrebbe). I miei test includevano sia microbenchmark (incluso munmap) sia anche "in application" in esecuzione in un caso d'uso reale. Naturalmente la mia applicazione non è la stessa della tua applicazione, quindi le persone dovrebbero testare localmente. Non è nemmeno chiaro che mmapsia favorito da un micro-benchmark: read()ottiene anche un grande impulso poiché il buffer di destinazione lato utente rimane generalmente in L1, il che potrebbe non accadere in un'applicazione più grande. Quindi sì, "è complicato".
BeeOnRope,

47

Il costo principale delle prestazioni sarà l'I / O su disco. "mmap ()" è certamente più veloce di istream, ma la differenza potrebbe non essere evidente perché l'I / O del disco dominerà i tuoi tempi di esecuzione.

Ho provato il frammento di codice di Ben Collins (vedi sopra / sotto) per testare la sua affermazione che "mmap () è molto più veloce" e non ho trovato alcuna differenza misurabile. Vedi i miei commenti sulla sua risposta.

Io certamente non consiglierei separatamente mmap'ing ogni record, a sua volta a meno che i "record" sono enormi - che sarebbe stato terribilmente lento, che richiede 2 chiamate di sistema per ogni record e, eventualmente, di perdere la pagina di fuori della cache del disco-memoria .... .

Nel tuo caso penso che mmap (), istream e le chiamate open () / read () di basso livello saranno tutte uguali. Consiglierei mmap () in questi casi:

  1. C'è un accesso casuale (non sequenziale) all'interno del file, AND
  2. il tutto si adatta comodamente alla memoria OPPURE all'interno del file è presente una località di riferimento in modo che determinate pagine possano essere mappate e altre pagine mappate. In questo modo il sistema operativo utilizza la RAM disponibile per il massimo beneficio.
  3. OPPURE se più processi stanno leggendo / lavorando sullo stesso file, mmap () è fantastico perché tutti i processi condividono le stesse pagine fisiche.

(a proposito - Adoro mmap () / MapViewOfFile ()).


Un buon punto sull'accesso casuale: questa potrebbe essere una delle cose che guida la mia percezione.
Ben Collins,

1
Non direi che il file deve adattarsi comodamente alla memoria, solo allo spazio degli indirizzi. Quindi sui sistemi a 64 bit, non ci dovrebbero essere motivi per non mappare file di grandi dimensioni. Il sistema operativo sa come gestirlo; è la stessa logica utilizzata per lo scambio, ma in questo caso non richiede spazio di scambio aggiuntivo sul disco.
MvG

@MvG: capisci il punto sull'i / o del disco? Se il file si adatta allo spazio degli indirizzi ma non alla memoria e si dispone di un accesso casuale, è possibile avere tutti gli accessi ai record che richiedono uno spostamento e una ricerca della testina del disco o un'operazione di pagina SSD, che sarebbe un disastro per le prestazioni.
Tim Cooper,

3
L'aspetto di I / O del disco deve essere indipendente dal metodo di accesso. Se si dispone di un accesso veramente casuale a file di dimensioni superiori alla RAM, sia mmap che seek + read sono fortemente associati al disco. Altrimenti entrambi trarranno beneficio dalle cache. Non vedo la dimensione del file rispetto alla dimensione della memoria come un argomento forte in entrambe le direzioni. La dimensione del file rispetto allo spazio degli indirizzi, d'altra parte, è un argomento molto forte, in particolare per l'accesso veramente casuale.
MvG,

La mia risposta originale aveva e ha questo punto: "il tutto si adatta comodamente alla memoria O c'è una località di riferimento all'interno del file". Quindi il secondo punto affronta ciò che stai dicendo.
Tim Cooper,

43

mmap è molto più veloce. Potresti scrivere un semplice benchmark per dimostrarlo a te stesso:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
  in.read(data, 0x1000);
  // do something with data
}

contro:

const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;

int fd = open("filename.bin", O_RDONLY);

while (off < file_size)
{
  data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
  // do stuff with data
  munmap(data, page_size);
  off += page_size;
}

Chiaramente, sto tralasciando i dettagli (come come determinare quando si raggiunge la fine del file nel caso in cui il file non sia un multiplo page_size, ad esempio), ma in realtà non dovrebbe essere molto più complicato di questo .

Se puoi, potresti provare a suddividere i tuoi dati in più file che possono essere mmap () - editi interamente anziché parzialmente (molto più semplici).

Un paio di mesi fa ho avuto un'implementazione a metà di un mmap () - classe di flusso a finestra scorrevole per boost_iostreams, ma a nessuno importava e mi sono impegnato con altre cose. Sfortunatamente, alcune settimane fa ho cancellato un archivio di vecchi progetti incompiuti, e quella era una delle vittime :-(

Aggiornamento : dovrei anche aggiungere l'avvertenza che questo benchmark sembrerebbe abbastanza diverso in Windows perché Microsoft ha implementato una cache di file elegante che fa la maggior parte di ciò che faresti con mmap in primo luogo. Vale a dire, per i file a cui si accede di frequente, si potrebbe semplicemente fare std :: ifstream.read () e sarebbe veloce come mmap, perché la cache dei file avrebbe già fatto un mapping di memoria per te, ed è trasparente.

Aggiornamento finale : guarda, gente: attraverso molte diverse combinazioni di piattaforme di SO e librerie e dischi standard e gerarchie di memoria, non posso dire con certezza che la chiamata di sistema mmap, vista come una scatola nera, sarà sempre sempre sostanzialmente più veloce di read. Non era esattamente il mio intento, anche se le mie parole potevano essere interpretate in quel modo. Alla fine, il mio punto era che gli I / O mappati in memoria sono generalmente più veloci degli I / O basati su byte; questo è ancora vero . Se scopri sperimentalmente che non c'è differenza tra i due, l'unica spiegazione che mi sembra ragionevole è che la tua piattaforma implementa la mappatura della memoria sotto le copertine in un modo che sia vantaggioso per le prestazioni delle chiamate aread. L'unico modo per essere assolutamente certi di utilizzare gli I / O mappati in memoria in modo portatile è utilizzare mmap. Se non ti interessa la portabilità e puoi fare affidamento sulle caratteristiche particolari delle tue piattaforme target, l'utilizzo readpotrebbe essere adatto senza sacrificare misurabilmente le prestazioni.

Modifica per pulire l'elenco delle risposte: @jbl:

la finestra scorrevole mmap sembra interessante. Puoi dirci qualcosa in più?

Certo - Stavo scrivendo una libreria C ++ per Git (un libgit ++, se vuoi), e ho riscontrato un problema simile a questo: avevo bisogno di essere in grado di aprire file di grandi dimensioni (molto grandi) e non avere prestazioni essere un cane totale (come sarebbe con std::fstream).

Boost::Iostreamsha già un sorgente mapped_file, ma il problema era che si trattava di mmapping di interi file, il che ti limita a 2 ^ (parole). Su macchine a 32 bit, 4 GB non sono abbastanza grandi. Non è irragionevole aspettarsi di avere .packfile in Git che diventano molto più grandi di così, quindi ho dovuto leggere il file in blocchi senza ricorrere al normale I / O di file. Sotto le coperte di Boost::Iostreams, ho implementato una Fonte, che è più o meno un'altra visione dell'interazione tra std::streambufe std::istream. Puoi anche provare un approccio simile ereditando semplicemente std::filebufin mapped_filebufe allo stesso modo, ereditando std::fstreamin a mapped_fstream. È l'interazione tra i due che è difficile da ottenere. Boost::Iostreams ha fatto parte del lavoro fatto per te e fornisce anche ganci per filtri e catene, quindi ho pensato che sarebbe stato più utile implementarlo in quel modo.


3
RE: cache file mmaped su Windows. Esatto: quando il buffering dei file è abilitato, la memoria del kernel mappa il file che stai leggendo internamente, lo legge nel buffer e lo copia nel tuo processo. È come se la memoria lo mappasse da solo, tranne con un passaggio di copia aggiuntivo.
Chris Smith,

6
Sono detestabile non essere d'accordo con una risposta accettata, ma credo che questa risposta sia sbagliata. Ho seguito il tuo suggerimento e provato il tuo codice, su una macchina Linux a 64 bit, e mmap () non era più veloce dell'implementazione STL. Inoltre, teoricamente non mi aspetterei che 'mmap ()' sia più veloce (o più lento).
Tim Cooper,

3
@Tim Cooper: potresti trovare questo thread ( markmail.org/message/… ) di interesse. Nota le due cose: mmap non è correttamente ottimizzato in Linux e per ottenere i migliori risultati è necessario anche utilizzare madvise nei loro test.
Ben Collins,

9
Caro Ben: ho letto quel link. Se 'mmap ()' non è più veloce su Linux e MapViewOfFile () non è più veloce su Windows, allora puoi affermare che "mmap è molto più veloce"? Inoltre, per ragioni teoriche credo che mmap () non sia più veloce per le letture sequenziali - hai qualche spiegazione del contrario?
Tim Cooper,

11
Ben, perché preoccuparsi di mmap()archiviare una pagina alla volta? Se a size_tè abbastanza capiente da contenere le dimensioni del file (molto probabilmente su sistemi a 64 bit), allora mmap()l'intero file in una sola chiamata.
Steve Emmerson,

39

Ci sono già molte buone risposte qui che coprono molti dei punti salienti, quindi aggiungerò solo un paio di problemi che non ho visto affrontati direttamente sopra. Cioè, questa risposta non dovrebbe essere considerata una panoramica di pro e contro, ma piuttosto un addendum ad altre risposte qui.

mmap sembra magico

Prendendo il caso in cui il file è già completamente memorizzato nella cache 1 come base 2 , mmappotrebbe sembrare praticamente magico :

  1. mmap richiede solo 1 chiamata di sistema per (potenzialmente) mappare l'intero file, dopo di che non sono necessarie altre chiamate di sistema.
  2. mmap non richiede una copia dei dati del file dal kernel allo spazio utente.
  3. mmapti consente di accedere al file "come memoria", incluso l'elaborazione con qualsiasi trucco avanzato che puoi fare contro la memoria, come vettorializzazione automatica del compilatore, intrinseci SIMD , prefetch, routine di analisi in memoria ottimizzate, OpenMP, ecc.

Nel caso in cui il file sia già nella cache, sembra impossibile da battere: basta accedere direttamente alla cache della pagina del kernel come memoria e non può essere più veloce di così.

Beh, può.

mmap non è in realtà magico perché ...

mmap funziona ancora per pagina

Un costo nascosto primario di mmapvs read(2)(che è in realtà il comparabile syscall a livello di sistema operativo per i blocchi di lettura ) è che con mmapte dovrai fare "un po 'di lavoro" per ogni pagina 4K nello spazio utente, anche se potrebbe essere nascosto dal meccanismo di errore di pagina.

Ad esempio, un'implementazione tipica che è solo mmapl'intero file dovrà essere inserita in modo errato, quindi 100 GB / 4K = 25 milioni di errori per leggere un file da 100 GB. Ora, questi saranno piccoli errori , ma 25 miliardi di errori di pagina non saranno ancora super veloci. Il costo di un errore minore è probabilmente nel centinaio di nanos nel migliore dei casi.

mmap fa molto affidamento sulle prestazioni TLB

Ora, puoi passare MAP_POPULATEa mmapper dirgli di impostare tutte le tabelle delle pagine prima di tornare, quindi non dovrebbero esserci errori di pagina durante l'accesso. Ora, questo ha il piccolo problema che legge anche l'intero file nella RAM, che esploderà se provi a mappare un file da 100 GB, ma per ora ignoralo 3 . Il kernel deve eseguire il lavoro per pagina per impostare queste tabelle di pagine (visualizzate come tempo del kernel). Questo finisce per essere un costo importante mmapnell'approccio, ed è proporzionale alla dimensione del file (cioè, non diventa relativamente meno importante con l'aumentare della dimensione del file) 4 .

Infine, anche nello spazio utente l'accesso a tale mappatura non è esattamente gratuito (rispetto ai buffer di memoria di grandi dimensioni che non provengono da un file-based mmap) - anche una volta impostate le tabelle delle pagine, ogni accesso a una nuova pagina verrà, concettualmente, incorre in una mancanza TLB. Poiché la mmapcreazione di un file implica l'utilizzo della cache delle pagine e delle sue pagine 4K, è necessario sostenere nuovamente questo costo 25 milioni di volte per un file da 100 GB.

Ora, il costo effettivo di questi mancati TLB dipende in gran parte almeno dai seguenti aspetti dell'hardware: (a) quante entità TLB 4K hai e come funziona il resto della cache di traduzione (b) con che cosa gestisce il prefetch hardware con il TLB - ad esempio, è possibile eseguire il prefetch per attivare una camminata di pagina? (c) quanto veloce e parallelo è l'hardware di camminata della pagina. Sui moderni processori Intel x86 di fascia alta, l'hardware di camminata di pagina è in generale molto forte: ci sono almeno 2 camminatori di pagine parallele, una camminata di pagina può avvenire in concomitanza con l'esecuzione continua e il prefetching dell'hardware può innescare una camminata di pagina. Pertanto, l'impatto di TLB su un carico in lettura in streaming è piuttosto basso e tale carico spesso si comporta in modo simile indipendentemente dalle dimensioni della pagina. L'altro hardware di solito è molto peggio!

read () evita queste insidie

Il read()syscall, che è ciò che generalmente sta alla base delle chiamate di tipo "blocco letto" offerte, ad esempio, in C, C ++ e altre lingue, presenta uno svantaggio principale di cui tutti sono ben consapevoli:

  • Ogni read()chiamata di N byte deve copiare N byte dal kernel nello spazio utente.

D'altra parte, evita la maggior parte dei costi di cui sopra - non è necessario mappare in 25 milioni di pagine 4K nello spazio utente. Di solito è possibile mallocun singolo buffer piccolo buffer nello spazio utente e riutilizzarlo ripetutamente per tutte le readchiamate. Dal lato del kernel, non c'è quasi nessun problema con le pagine 4K o i mancati TLB perché tutta la RAM è solitamente mappata linearmente usando poche pagine molto grandi (ad esempio, pagine da 1 GB su x86), quindi le pagine sottostanti nella cache delle pagine sono coperte molto efficientemente nello spazio del kernel.

Quindi fondamentalmente hai il seguente confronto per determinare quale è più veloce per una singola lettura di un file di grandi dimensioni:

Il lavoro extra per pagina implicato mmapdall'approccio è più costoso del lavoro per byte della copia del contenuto dei file dal kernel nello spazio utente implicito usando read()?

Su molti sistemi, in realtà sono approssimativamente bilanciati. Si noti che ognuno ridimensiona con attributi completamente diversi dello stack hardware e del sistema operativo.

In particolare, l' mmapapproccio diventa relativamente più veloce quando:

  • Il sistema operativo ha una rapida gestione dei guasti minori e in particolare ottimizzazioni di carica per guasti minori come guasti.
  • Il sistema operativo ha una buona MAP_POPULATEimplementazione che può elaborare in modo efficiente mappe di grandi dimensioni nei casi in cui, ad esempio, le pagine sottostanti sono contigue nella memoria fisica.
  • L'hardware offre ottime prestazioni di traduzione delle pagine, come TLB di grandi dimensioni, TLB veloci di secondo livello, page walker veloci e paralleli, buona interazione di prefetch con la traduzione e così via.

... mentre l' read()approccio diventa relativamente più veloce quando:

  • Il read()syscall ha buone prestazioni di copia. Ad esempio, buone copy_to_userprestazioni sul lato kernel.
  • Il kernel ha un modo efficiente (relativamente all'area utente) per mappare la memoria, ad esempio usando solo poche pagine di grandi dimensioni con supporto hardware.
  • Il kernel ha syscalls veloci e un modo per mantenere le voci TLB del kernel attraverso syscalls.

I fattori hardware sopra descritti variano notevolmente tra piattaforme diverse, anche all'interno della stessa famiglia (ad esempio, entro x86 generazioni e in particolare segmenti di mercato) e sicuramente tra architetture (ad esempio, ARM vs x86 vs PPC).

Anche i fattori del sistema operativo continuano a cambiare, con vari miglioramenti su entrambi i lati che causano un grande salto nella velocità relativa per un approccio o l'altro. Un elenco recente include:

  • Aggiunta di un errore, descritto sopra, che aiuta davvero il mmapcaso senza MAP_POPULATE.
  • Aggiunta di copy_to_usermetodi di percorso rapido arch/x86/lib/copy_user_64.S, ad esempio, REP MOVQquando è veloce, il che aiuta davvero il read()caso.

Aggiornamento dopo Spectre e Meltdown

Le mitigazioni delle vulnerabilità di Spectre e Meltdown hanno aumentato notevolmente il costo di una chiamata di sistema. Sui sistemi che ho misurato, il costo di una chiamata di sistema "non fare nulla" (che è una stima del puro sovraccarico della chiamata di sistema, a parte qualsiasi lavoro effettivo svolto dalla chiamata) è passato da circa 100 ns su un tipico moderno sistema Linux a circa 700 ns. Inoltre, a seconda del sistema in uso, la correzione dell'isolamento della tabella delle pagine appositamente per Meltdown può avere effetti downstream aggiuntivi oltre al costo delle chiamate dirette del sistema a causa della necessità di ricaricare le voci TLB.

Tutto ciò rappresenta uno svantaggio relativo per i read()metodi basati rispetto ai mmapmetodi basati, poiché i read()metodi devono effettuare una chiamata di sistema per ciascun valore di "dimensioni del buffer" di dati. Non è possibile aumentare in modo arbitrario la dimensione del buffer per ammortizzare questo costo poiché l'utilizzo di buffer di grandi dimensioni di solito ha prestazioni peggiori poiché si supera la dimensione L1 e quindi si verificano costantemente perdite di cache.

D'altra parte, con mmap, è possibile mappare in una vasta area di memoria con MAP_POPULATEe accedervi in ​​modo efficiente, al costo di una sola chiamata di sistema.


1 Questo include più o meno anche il caso in cui il file non era completamente memorizzato nella cache per iniziare, ma in cui il sistema operativo read-ahead è abbastanza buono da farlo apparire così (cioè, la pagina viene solitamente memorizzata nella cache quando lo voglio). Questo è un problema sottile, tuttavia, poiché il modo in cui funziona il read-ahead è spesso abbastanza diverso tra mmape readchiamate e può essere ulteriormente regolato da chiamate "avvisare" come descritto in 2 .

2 ... perché se il file non viene memorizzato nella cache, il tuo comportamento sarà completamente dominato dalle preoccupazioni di I / O, incluso quanto sia comprensivo il tuo modello di accesso all'hardware sottostante - e tutti i tuoi sforzi dovrebbero essere nel garantire che tale accesso sia comprensivo come possibile, ad es. tramite l'uso madviseo le fadvisechiamate (e qualsiasi modifica del livello dell'applicazione sia possibile apportare per migliorare i modelli di accesso).

3 Potresti aggirare il problema, ad esempio mmapinserendo sequenzialmente finestre di dimensioni inferiori, ad esempio 100 MB.

4 In effetti, si scopre che l' MAP_POPULATEapproccio è (almeno una combinazione hardware / sistema operativo) solo leggermente più veloce rispetto al non utilizzo, probabilmente perché il kernel sta usando un errore - quindi il numero effettivo di errori minori è ridotto di un fattore 16 o così.


4
Grazie per aver fornito una risposta più sfumata a questo problema complesso. Sembra ovvio alla maggior parte delle persone che mmap è più veloce, quando in realtà spesso non è così. Nei miei esperimenti, l'accesso casuale a un grande database da 100 GB con un indice in memoria si è rivelato più veloce con pread (), anche se stavo mallocando un buffer per ciascuno dei milioni di accessi. E sembra che un sacco di persone nell'industria abbiano osservato lo stesso .
Caetano Sauer,

5
Sì, dipende molto dallo scenario. Se le letture sono abbastanza piccole e con il passare del tempo si tende a leggere ripetutamente gli stessi byte, si mmapotterrà un vantaggio insormontabile poiché si evita l'overhead delle chiamate del kernel fisse. D'altra parte, mmapaumenta anche la pressione del TLB e in realtà rende più lento la fase di "riscaldamento" in cui i byte vengono letti per la prima volta nel processo corrente (sebbene siano ancora nella pagina della pagina), poiché potrebbe farlo più lavoro di read, ad esempio, per "sfogliare" le pagine adiacenti ... e per le stesse applicazioni "riscaldare" è tutto ciò che conta! @CaetanoSauer
BeeOnRope

Penso dove dici "... ma 25 miliardi di errori di pagina non saranno ancora super veloci ..." dovrebbe leggere "... ma 25 milioni di errori di pagina non saranno ancora super veloci ..." . Non sono positivo al 100%, per questo motivo non sto modificando direttamente.
Ton van den Heuvel,

7

Mi dispiace che Ben Collins abbia perso il suo codice sorgente mmap di Windows scorrevole. Sarebbe bello avere in Boost.

Sì, la mappatura del file è molto più veloce. Stai essenzialmente usando il sottosistema di memoria virtuale del sistema operativo per associare la memoria su disco e viceversa. Pensaci in questo modo: se gli sviluppatori del kernel del sistema operativo potessero farlo più velocemente, lo farebbero. Perché farlo rende quasi tutto più veloce: database, tempi di avvio, tempi di caricamento del programma, ecc.

L'approccio a finestra scorrevole non è poi così difficile in quanto più pagine contigue possono essere mappate contemporaneamente. Quindi le dimensioni del record non contano fino a quando il più grande di ogni singolo record si adatta alla memoria. L'importante è gestire la contabilità.

Se un record non inizia al limite di getpagesize (), la mappatura deve iniziare nella pagina precedente. La lunghezza della regione mappata si estende dal primo byte del record (arrotondato per difetto, se necessario, al multiplo più vicino di getpagesize ()) all'ultimo byte del record (arrotondato per eccesso al multiplo più vicino di getpagesize ()). Al termine dell'elaborazione di un record, è possibile decomprimerlo () e passare al successivo.

Tutto questo funziona bene anche con Windows usando CreateFileMapping () e MapViewOfFile () (e GetSystemInfo () per ottenere SYSTEM_INFO.dwAllocationGranularity --- non SYSTEM_INFO.dwPageSize).


Ho appena cercato su Google e ho trovato questo piccolo frammento di dwAllocationGranularity - Stavo usando dwPageSize e tutto si stava rompendo. Grazie!
wickedchicken,

4

mmap dovrebbe essere più veloce, ma non so quanto. Dipende molto dal tuo codice. Se usi mmap è meglio mmap l'intero file in una volta, questo ti renderà la vita molto più semplice. Un potenziale problema è che se il tuo file è più grande di 4 GB (o in pratica il limite è inferiore, spesso 2 GB) avrai bisogno di un'architettura a 64 bit. Quindi, se stai usando un ambiente 32, probabilmente non vuoi usarlo.

Detto questo, potrebbe esserci una strada migliore per migliorare le prestazioni. Hai detto che il file di input viene scansionato molte volte , se riesci a leggerlo in un solo passaggio e poi farlo con esso, potrebbe potenzialmente essere molto più veloce.


3

Forse dovresti pre-elaborare i file, quindi ogni record è in un file separato (o almeno che ogni file ha una dimensione di mmap).

Potresti anche eseguire tutte le fasi di elaborazione per ciascun record, prima di passare a quello successivo? Forse questo eviterebbe un po 'dell'overhead IO?


3

Sono d'accordo che l'I / O del file mmap'd sarà più veloce, ma mentre si fa il benchmark del codice, l'esempio del contatore non dovrebbe essere in qualche modo ottimizzato?

Ben Collins ha scritto:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
    in.read(data, 0x1000);
    // do something with data 
}

Suggerirei anche di provare:

char data[0x1000];
std::ifstream iifle( "file.bin");
std::istream  in( ifile.rdbuf() );

while( in )
{
    in.read( data, 0x1000);
    // do something with data
}

E oltre a ciò, potresti anche provare a rendere le dimensioni del buffer della stessa dimensione di una pagina di memoria virtuale, nel caso in cui 0x1000 non sia la dimensione di una pagina di memoria virtuale sul tuo computer ... IMHO mmap'd I / O dei file ancora vince, ma questo dovrebbe avvicinare le cose.


2

A mio avviso, l'utilizzo di mmap () "solo" scosta lo sviluppatore dal dover scrivere il proprio codice di memorizzazione nella cache. In un semplice caso di "lettura rapida del file una volta sola", questo non sarà difficile (anche se, come sottolinea mlbrock, si salva comunque la copia di memoria nello spazio del processo), ma se si va avanti e indietro nel file o saltare i bit e così via, credo che gli sviluppatori del kernel abbiano probabilmente fatto un lavoro migliore nell'implementazione della cache di quanto io possa ...


1
Molto probabilmente puoi fare un lavoro migliore nella memorizzazione nella cache dei dati specifici dell'applicazione rispetto al kernel, che opera su blocchi di dimensioni di pagina in un modo molto cieco (ad esempio, utilizza solo un semplice schema pseudo-LRU per decidere quali pagine sfrattare ) - mentre potresti sapere molto sulla giusta granularità della cache e avere anche una buona idea dei futuri modelli di accesso. Il vero vantaggio della mmapmemorizzazione nella cache è che riutilizzi semplicemente la cache di pagina esistente che sarà già lì, in modo da ottenere quella memoria gratuitamente e può essere condivisa anche tra i processi.
BeeOnRope,

2

Ricordo di aver mappato un enorme file contenente una struttura ad albero nella memoria anni fa. Sono stato sorpreso dalla velocità rispetto alla normale serializzazione che comporta molto lavoro in memoria, come l'allocazione dei nodi dell'albero e l'impostazione dei puntatori. Quindi, in effetti, stavo confrontando una singola chiamata a mmap (o la sua controparte su Windows) con molte (MOLTE) chiamate a chiamate di operatori nuove e costruttive. Per questo tipo di attività, mmap è imbattibile rispetto alla deserializzazione. Ovviamente si dovrebbe esaminare il puntatore rilocabile di boost per questo.


Sembra più una ricetta per il disastro. Cosa fai se cambia il layout dell'oggetto? Se hai funzioni virtuali, probabilmente tutti i puntatori vftbl saranno sbagliati. Come si controlla dove viene mappato il file? Puoi dargli un indirizzo, ma è solo un suggerimento e il kernel può scegliere un altro indirizzo di base.
Jens,

Funziona perfettamente quando si dispone di una struttura ad albero stabile e chiaramente definita. Quindi puoi trasmettere tutto alle tue strutture rilevanti e seguire i puntatori del file interno aggiungendo ogni volta un offset di "indirizzo iniziale mmap". Questo è molto simile ai file system che usano gli inode e gli alberi delle directory
Mike76,

1

Sembra un buon caso d'uso per il multi-threading ... Penso che potresti facilmente configurare un thread per leggere i dati mentre gli altri li elaborano. Potrebbe essere un modo per aumentare notevolmente le prestazioni percepite. Solo un pensiero.


Sì. Ci ho pensato e probabilmente lo proverò in una versione successiva. L'unica riserva che ho è che l'elaborazione è molto più breve della latenza I / O, quindi potrebbero non esserci molti vantaggi.
jbl,

1

Penso che la cosa più grande di mmap sia il potenziale per la lettura asincrona con:

    addr1 = NULL;
    while( size_left > 0 ) {
        r = min(MMAP_SIZE, size_left);
        addr2 = mmap(NULL, r,
            PROT_READ, MAP_FLAGS,
            0, pos);
        if (addr1 != NULL)
        {
            /* process mmap from prev cycle */
            feed_data(ctx, addr1, MMAP_SIZE);
            munmap(addr1, MMAP_SIZE);
        }
        addr1 = addr2;
        size_left -= r;
        pos += r;
    }
    feed_data(ctx, addr1, r);
    munmap(addr1, r);

Il problema è che non riesco a trovare il MAP_FLAGS giusto per dare un suggerimento che questa memoria dovrebbe essere sincronizzata dal file al più presto. Spero che MAP_POPULATE dia il giusto suggerimento per mmap (cioè non proverà a caricare tutto il contenuto prima di tornare dalla chiamata, ma lo farà in modo asincrono con feed_data). Almeno dà risultati migliori con questo flag anche se quel manuale afferma che non fa nulla senza MAP_PRIVATE dal 2.6.23.


2
Vuoi posix_madvisecon laWILLNEED bandiera per i suggerimenti pigri da prepopolare.
ShadowRanger,

@ShadowRanger, sembra ragionevole. Anche se aggiornerei la pagina man per dichiarare chiaramente che posix_madviseè una chiamata asincrona. Sarebbe anche bello fare riferimento mlocka coloro che vogliono aspettare fino a quando l'intera area di memoria sarà disponibile senza errori di pagina.
ony
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.