Come possono i dati scritti su un file in realtà essere svuotati/sincronizzati con il dispositivo di blocco da Java.Realizza forzatamente la sincronizzazione/flush dei file in Java
ho provato questo codice con NIO:.
FileOutputStream s = new FileOutputStream(filename)
Channel c = s.getChannel()
while(xyz)
c.write(buffer)
c.force(true)
s.getFD().sync()
c.close()
ho supposto che c.force (vero) togehter con s.getFD() sync() dovrebbe essere sufficiente perché il doc per force stati
Forza gli eventuali aggiornamenti al file di questo canale da scrivere sul dispositivo di archiviazione che lo contiene. Se il file di questo canale risiede su un dispositivo di archiviazione locale, allora, quando questo metodo restituisce è garantito che tutte le modifiche apportate al file dal momento che questo canale è stato creato, oppure dal momento che questo metodo è stato ultima Richiamato, sarà stato scritto a quel dispositivo. Questo è utile per garantire che le informazioni critiche non vengano perse in caso di arresto anomalo del sistema.
La documentazione per sync stati:
Forza tutti i buffer di sistema per la sincronizzazione con il dispositivo sottostante. Questo metodo ritorna dopo che tutti i dati e gli attributi modificati di questo FileDescriptor sono stati scritti sui dispositivi pertinenti. In particolare, se questo FileDescriptor si riferisce ad un supporto fisico, ad esempio un file in un file di sistema, la sincronizzazione non restituirà finché tutte le copie in memoria modificate del buffer associati a questo FileDesecriptor sono stati scritti sul supporto fisico. la sincronizzazione è pensata per essere utilizzata dal codice che richiede che l'archiviazione fisica (come un file) sia in uno stato noto.
Queste due chiamate dovrebbero essere sufficienti. È? Immagino che non lo siano.
Background: faccio un piccolo confronto delle prestazioni (2 GB, scrittura sequenziale) usando C/Java e la versione Java è due volte più veloce della versione C e probabilmente più veloce dell'hardware (120 MB/s su un singolo HD). Ho anche provato a eseguire la sincronizzazione strumento a riga di comando con Runtime.getRuntime(). Exec ("sync"), ma che non è cambiato il comportamento.
Il codice C conseguente 70 MB/s è (utilizzando le API di basso livello (aperto, scrivere, close) non cambia molto):
FILE* fp = fopen(filename, "w");
while(xyz) {
fwrite(buffer, 1, BLOCK_SIZE, fp);
}
fflush(fp);
fclose(fp);
sync();
Senza la chiamata finale per la sincronizzazione; Ho ricevuto valori non pertinenti (oltre 1 GB, ovvero le prestazioni della memoria principale).
Perché c'è una grande differenza tra C e Java? Ci sono due possibilità: io non sincronizzo correttamente i dati in Java o il codice C non è ottimale per qualche motivo.
Aggiornamento: Ho eseguito strace con "strace -cfT cmd". Ecco i risultati:
C (basso livello API): MB/s 67,389782
% time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 87.21 0.200012 200012 1 fdatasync 11.05 0.025345 1 32772 write 1.74 0.004000 4000 1 sync
C (ad alto livello API): MB/s 61,796458
% time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 73.19 0.144009 144009 1 sync 26.81 0.052739 1 65539 write
Java (1.6 SUN JRE, API java.io): MB/s 128.6755466197537
% time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 80.07 105.387609 3215 32776 write 2.58 3.390060 3201 1059 read 0.62 0.815251 815251 1 fsync
Java (1.6 Sun JRE, API java.nio): MB/s 127,45830221558376
5.52 0.980061 490031 2 fsync 1.60 0.284752 9 32774 write 0.00 0.000000 0 80 close
Il tempo valori sembrano essere solo il tempo di sistema e sono quindi abbastanza insignificante.
Aggiornamento 2: Sono passato a un altro server, riavviato e utilizzo un ext3 formattato di recente. Ora ho solo il 4% di differenze tra Java e C. Semplicemente non so cosa sia andato storto. A volte le cose sono strane. Avrei dovuto provare la misurazione con un altro sistema prima di scrivere questa domanda. Scusate.
Update 3: Per riassumere le risposte:.
- Usa c.force (veri) seguito da s.getFD() sync() per Java NIO e s.flush() e s.getFD() .sync() per Java stream API. Per l'API di alto livello in C non dimenticare di sincronizzare. Un fflush ha inviato i dati al sistema operativo, ma non trasferisce i dati sul dispositivo a blocchi.
- Utilizzare strace per analizzare le syscalls eseguite da un comando
- Controllare i risultati prima di inviare una domanda.
Aggiornamento 4: Si prega di notare il seguente follow-up question.
Mi piacerebbe molto vedere il throughput utilizzando solo le funzioni della sezione 2. –
Che cosa stai utilizzando per BLOCK_SIZE? Ha le stesse dimensioni del tuo buffer in Java? 512 sarà molto subottimale in questi giorni. Probabilmente vorrai almeno 4096 (dimensione della pagina su x86) o possibilmente superiore. Ho visto miglioramenti misurabili fino a 32k su alcune macchine. Oh, e ovviamente se il tuo buffer è allineato alla pagina darà al kernel più spazio per l'ottimizzazione. – aij
Un altro possibile problema è che il codice che hai postato non utilizza le "API di basso livello (apri, scrivi, chiudi)". Sta usando l'API di stdio portatile di livello superiore (fopen, fwrite, fclose) che aggiungerà un ulteriore livello di buffering di default. Hai disattivato esplicitamente il buffering da qualche parte al di fuori del codice che hai postato? – aij