Prestazioni/stabilità di un file mappato in memoria - Native o MappedByteBuffer - vs.il semplice vecchio FileOutputStream

StackOverflow https://stackoverflow.com/questions/537295

Domanda

Supporto un'applicazione Java legacy che utilizza file flat (testo normale) per la persistenza.A causa della natura dell'applicazione, la dimensione di questi file può raggiungere i 100 MB al giorno e spesso il fattore limitante nelle prestazioni dell'applicazione è l'IO del file.Attualmente, l'applicazione utilizza un semplice java.io.FileOutputStream per scrivere i dati su disco.

Recentemente, diversi sviluppatori hanno affermato che l'utilizzo di file mappati in memoria, implementati in codice nativo (C/C++) e accessibili tramite JNI, fornirebbe prestazioni migliori.Tuttavia, FileOutputStream utilizza già metodi nativi per i suoi metodi principali (ad es.write(byte[])), quindi sembra un presupposto debole senza dati concreti o almeno prove aneddotiche.

Ho diverse domande a riguardo:

  1. Questa affermazione è davvero vera?I file verranno mappati in memoria SempreFornire IO più veloce rispetto a FileOutputStream di Java?

  2. La classe MappedByTeBuffer accessibile da un filechannel fornisce la stessa funzionalità di una libreria di file mappata di memoria nativa a cui si accede tramite JNI?Che cosa manca MappingByteBuffer che potrebbe portarti a usare una soluzione JNI?

  3. Quali sono i rischi di utilizzare i file mappati da memoria per il disco IO in un'applicazione di produzione?Cioè, applicazioni che hanno un tempo di attività continue con riavvii minimi (una volta al mese, max).Preferiscono aneddoti della vita reale dalle applicazioni di produzione (Java o altro).

La domanda n. 3 è importante: potrei rispondere io stesso a questa domanda parzialmente scrivendo un'applicazione "giocattolo" che esegue test IO utilizzando le varie opzioni descritte sopra, ma pubblicando su SO spero in aneddoti/dati del mondo reale su cui riflettere.

[MODIFICA] Chiarimento: ogni giorno di attività, l'applicazione crea più file di dimensioni variabili da 100 MB a 1 giga.In totale, l'applicazione potrebbe scrivere più giga di dati al giorno.

È stato utile?

Soluzione

Potresti essere in grado di accelerare un po' le cose esaminando il modo in cui i tuoi dati vengono memorizzati nel buffer durante le scritture.Questo tende ad essere specifico dell'applicazione poiché avresti bisogno di un'idea dei modelli di scrittura dei dati previsti.Se la coerenza dei dati è importante, ci saranno dei compromessi qui.

Se stai semplicemente scrivendo nuovi dati su disco dalla tua applicazione, l'I/O mappato in memoria probabilmente non sarà di grande aiuto.Non vedo alcun motivo per cui vorresti investire tempo in qualche soluzione nativa codificata personalizzata.Sembra semplicemente che la tua applicazione sia troppo complessa, da quello che hai fornito finora.

Se sei sicuro di aver davvero bisogno di migliori prestazioni I/O - o semplicemente di prestazioni O nel tuo caso, esaminerei una soluzione hardware come un array di dischi sintonizzati.Utilizzare più hardware per risolvere il problema è spesso molto più conveniente dal punto di vista aziendale che dedicare tempo all'ottimizzazione del software.Di solito è anche più veloce da implementare e più affidabile.

In generale, ci sono molte insidie ​​​​nell'ottimizzazione eccessiva del software.Introdurrai nuovi tipi di problemi nella tua applicazione.Potresti riscontrare problemi di memoria/thrashing GC che porterebbero a maggiore manutenzione/ottimizzazione.La parte peggiore è che molti di questi problemi saranno difficili da testare prima di entrare in produzione.

Se fosse la mia app, probabilmente resterei con FileOutputStream con un buffer eventualmente ottimizzato.Dopodiché utilizzerei la soluzione ormai consolidata di utilizzare più hardware.

Altri suggerimenti

Memory mapped I / O non renderà i dischi più veloci (!). Per l'accesso lineare, sembra un po 'inutile.

A mappato tampone NIO è la cosa reale (usuale avvertimento qualsiasi attuazione ragionevole).

Come con altri buffer NIO diretta allocati, i buffer non sono normali memoria e non otterranno GCed nel modo più efficiente. Se si creano molti di loro si potrebbe scoprire che si esaurisce lo spazio di memoria / indirizzo senza esaurire heap Java. Questa è ovviamente una preoccupazione con processi in esecuzione lunghi.

Dalla mia esperienza, la memoria mappata file eseguire molto meglio di quanto l'accesso ai file in pianura sia in tempo reale e di usare la persistenza dei casi. Ho lavorato principalmente con il C ++ su Windows, ma le prestazioni di Linux sono simili, e hai intenzione di usare JNI comunque, quindi penso che si applica al vostro problema.

Per un esempio di un motore di persistenza costruito su file di memoria mappata, vedere Metakit . L'ho usato in un'applicazione in cui gli oggetti sono stati semplici vedute dati mappati in memoria, il motore ha preso cura di tutte le cose mappatura dietro le tende. Questo era sia veloce ed efficiente della memoria (almeno rispetto agli approcci tradizionali, come quelle della precedente versione utilizzata), e ci siamo commettiamo transazioni / rollback gratuitamente.

In un altro progetto che ho dovuto scrivere applicazioni di rete multicast. I dati sono stati invia in modo randomizzato per minimizzare l'impatto di perdita di pacchetti consecutivi (combinato con FEC regimi e di blocco). Inoltre i dati potrebbero ben superano lo spazio di indirizzi (file video erano più grandi di 2 GB) in modo allocazione di memoria era fuori discussione. Sul lato server, sezioni del file sono mappati in memoria su richiesta e il livello di rete raccolte direttamente i dati da questi punti di vista; di conseguenza l'utilizzo della memoria è stata molto bassa. Sul lato del ricevitore, non c'era modo di prevedere l'ordine in cui sono stati ricevuti i pacchetti, quindi deve mantenere un numero limitato di viste attive sul file di destinazione e dati sono stati copiati direttamente in queste viste. Quando un pacchetto doveva essere messo in una zona non mappata, la vista più antica era mappata (ed eventualmente lavata nel file dal sistema) e sostituito da una nuova visione sulla zona di destinazione. Le prestazioni sono stati eccezionali, in particolare perché il sistema ha fatto un ottimo lavoro a commettere i dati come attività in background, e vincoli di tempo reale sono stati facilmente soddisfatti.

Da allora sono convinto che anche il miglior sistema di software di fine-artigianale non può battere la politica di I / O di default del sistema con file mappato in memoria, perché il sistema sa più di applicazioni user-space su quando e come i dati devono essere scritto. Inoltre, ciò che è importante sapere è che la mappatura della memoria è un must quando si tratta di dati di grandi dimensioni, in quanto i dati non viene mai assegnato (quindi consumando memoria), ma mappato dinamicamente nello spazio di indirizzi, e gestito da gestore della memoria virtuale del sistema, che è sempre più veloce del mucchio. Così il sistema di utilizzare sempre la memoria in modo ottimale, e si impegna i dati ogni volta che deve, alle spalle dell'applicazione senza impattare esso.

Speranza che aiuta.

Per quanto riguarda il punto 3 - se la macchina si blocca e ci sono tutte le pagine che non sono state scritte su disco, poi si sono persi. Un'altra cosa è lo spreco di spazio degli indirizzi - mappare un file nella memoria consuma spazio di indirizzamento (e richiede area contigua), e bene, su macchine a 32 bit è un po 'limitato. Ma che hai detto circa 100MB - quindi non dovrebbe essere un problema. E un'altra cosa -. Espandendo la dimensione del file mmaped richiede un po 'di lavoro

A proposito, questo SO discussione può anche darvi alcune intuizioni.

Ho fatto un studio dove io paragono le prestazioni di scrittura ad un ByteBuffer grezzo rispetto le prestazioni di scrittura ad un MappedByteBuffer. file mappati in memoria sono supportati dal sistema operativo e le loro latenze di scrittura sono molto buone, come si può vedere nel mio numero di riferimento. Esecuzione di scritture sincrone attraverso un FileChannel è di circa 20 volte più lento e questo è il motivo per cui le persone fanno la registrazione asincrona per tutto il tempo. Nel mio studio ho anche un esempio di come implementare la registrazione asincrona attraverso una coda di lock-libera e senza spazzatura per l'ultima prestazione molto vicino a un ByteBuffer crudo.

Se si scrive meno byte sarà più veloce. Che cosa succede se filtrato attraverso GZIPOutputStream, o quello che se hai scritto i dati in file zip o jarfiles?

Come accennato in precedenza, utilizzare NIO (anche noto come nuova IO). C'è anche un nuovo, nuova IO uscendo.

L'uso corretto di una soluzione di disco rigido RAID avrebbe aiutato, ma che sarebbe un dolore.

Mi piace molto l'idea di compressione dei dati. Andare per il tizio GZIPOutputStream! Che sarebbe raddoppiare la produttività se la CPU può tenere il passo. E 'probabile che si può approfittare delle macchine a doppio nucleo ormai standard, eh?

-Stosh

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top