2013-03-28 5 views
20

Stiamo sperimentando con la modifica di SQLite, un sistema di database incorporato, per utilizzare mmap() invece delle solite chiamate read() e write() per accedere al database file su disco. Utilizzo di un'unica mappatura di grandi dimensioni per l'intero file . Supponiamo che il file sia abbastanza piccolo da non aver problemi trovare spazio per questo nella memoria virtuale.Come estendere un file accessibile tramite mmap()

Fin qui tutto bene. In molti casi l'uso di mmap() sembra essere un po 'più veloce rispetto a read() e write(). E in alcuni casi molto più veloce.

Il ridimensionamento della mappatura al fine di eseguire il commit di una transazione di scrittura che estenda il file di database sembra essere un problema. Al fine di estendere il file di database, il codice potrebbe fare qualcosa di simile:

ftruncate(); // extend the database file on disk 
    munmap();  // unmap the current mapping (it's now too small) 
    mmap();   // create a new, larger, mapping 

quindi copiare i nuovi dati nella fine della nuova mappatura della memoria. Tuttavia, il munmap/mmap non è desiderabile poiché significa che la prossima volta che si accede a ciascuna pagina del file di database si verifica un errore di pagina minore e il sistema deve cercare nella cache della pagina del sistema operativo il frame corretto su associato al virtuale indirizzo di memoria. In altre parole, rallenta durante le letture successive del database.

Su Linux, è possibile utilizzare la chiamata di sistema mremap() non standard anziché di munmap()/mmap() per ridimensionare la mappatura. Ciò sembra evitare gli errori di pagina minore .

DOMANDA: Come dovrebbe essere affrontato su altri sistemi, come OSX, che non dispongono di mremap()?


Abbiamo due idee al momento. E una domanda su ciascuno:

1) Creare mappature più grandi del file di database. Quindi, quando si estende il file di database , è sufficiente chiamare ftruncate() per estendere il file sul disco e continuare a utilizzare la stessa mappatura.

Questo sarebbe l'ideale, e sembra funzionare nella pratica. Tuttavia, siamo preoccupati per questo avviso nella pagina man:

"L'effetto della modifica della dimensione del file di fondo di un mappatura sulle pagine che corrispondono a regioni aggiunti o rimossi del il file non è specificato ".

DOMANDA: È qualcosa di cui dovremmo essere preoccupati? O un anacronismo a questo punto?

2) Quando si estende il file di database, utilizzare il primo argomento di mmap() richiesta di mappatura corrispondente alle nuove pagine del file di database situato immediatamente dopo la mappatura corrente a memoria virtuale. Estendendo in modo efficace la mappatura iniziale. Se il sistema non può onorare la richiesta di collocare immediatamente la nuova mappatura dopo , tornare a munmap/mmap.

In pratica, abbiamo rilevato che OSX è abbastanza buono per il posizionamento dei mapping in questo modo, quindi questo trucco funziona lì.

DOMANDA: se il sistema non allocare la seconda mappatura immediatamente successivo al primo nella memoria virtuale, è quindi sicuro casualmente unmap entrambi utilizzando un unico grande chiamata a munmap()?

+0

Ho fatto esattamente la stessa cosa. Su Solaris 10 'munmap' fa un' sincrono' msync' se ricordo male. Infatti 'msync' è sempre stato sincronizzato su Solaris 10 anche quando è stato specificato' MS_ASYNC'. Questi erano un paio degli ultimi chiodi nella bara di Solaris. –

+0

Non credo che il # 1 sia fattibile. La creazione di una mappatura più grande del risultato del file nella coda del file non è accessibile (sebbene possa essere "mappata"), e 'ftruncate()' non aggiornerà la mappatura. – twalberg

risposta

3
  1. Penso che # 2 sia la migliore soluzione attualmente disponibile. Oltre a questo, su sistemi a 64 bit è possibile creare esplicitamente il proprio mapping su un indirizzo che il sistema operativo non sceglierebbe mai per una mappatura (ad esempio 0x6000 0000 0000 0000 in Linux) per evitare che il sistema operativo non possa posizionare il nuovo mapping immediatamente dopo il primo uno.

  2. È sempre possibile non mappare mappature mutiple con una singola chiamata munmap. Puoi anche annullare la mappatura di una parte della mappatura se desideri farlo.

+6

la maggior parte delle implementazioni a 64 bit nel mondo reale (cioè cpus effettive) non supportano spazi di indirizzi a 64 bit. ad esempio, nessuno dei cpu amd64 esistenti supporta l'indirizzo 0x6000 0000 0000 0000. –

4
  1. Usa fallocate() al posto di ftruncate(), se disponibili. In caso contrario, apri il file in modalità O_APPEND e aumenta il file scrivendo un certo numero di zeri. Ciò riduce notevolmente la frammentazione.

  2. Utilizzare "Pagine grandi", se disponibile, per ridurre notevolmente il sovraccarico su mappature grandi.

  3. pread()/pwrite()/pwritev()/preadv() con dimensioni del blocco non troppo piccole non è molto lento. Molto più veloce di IO può essere effettivamente eseguito.

  4. Gli errori IO quando si utilizza mmap() generano solo segfault anziché EIO o così.

  5. La maggior parte dei problemi delle prestazioni di SQLite WRITE è concentrata in un buon uso delle transazioni (ad esempio, dovresti eseguire il debug quando COMMIT viene effettivamente eseguito).

+3

L'uso di 'fallocate()' disattiva l'allocazione ritardata, forzando la ricerca di dischi e gli aggiornamenti dei metadati per allocare immediatamente i blocchi fisici per la nuova area del file, piuttosto che consentire l'allocazione quando le pagine sporche vengono successivamente scaricate. Infatti, usando 'fallocate()' può * peggiorare * la frammentazione se più file vengono estesi contemporaneamente: finirai con i loro blocchi interlacciati su disco. In genere, si dovrebbe usare solo "fallocate()" per preallocare un file di grandi dimensioni di cui si conosce la dimensione in anticipo (come un file da copiare o scaricare). –