2009-04-08 11 views
29

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.

+0

Mi piacerebbe molto vedere il throughput utilizzando solo le funzioni della sezione 2. –

+0

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

+0

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

risposta

2

È necessario dirci di più sull'hardware e sul sistema operativo, anche sulla specifica versione Java. Come stai misurando questo throughput?

Sei corretto che forza/sincronizzazione dovrebbe forzare i dati sul supporto fisico.


Ecco una versione originale di copia. Compilato con gcc 4.0 su un Mac Intel, dovrebbe essere pulito.

/* rawcopy -- pure C, system calls only, copy argv[1] to argv[2] */ 

/* This is a test program which simply copies from file to file using 
* only system calls (section 2 of the manual.) 
* 
* Compile: 
* 
*  gcc -Wall -DBUFSIZ=1024 -o rawcopy rawcopy.c 
* 
* If DIRTY is defined, then errors are interpreted with perror(3). 
* This is ifdef'd so that the CLEAN version is free of stdio. For 
* convenience I'm using BUFSIZ from stdio.h; to compile CLEAN just 
* use the value from your stdio.h in place of 1024 above. 
* 
* Compile DIRTY: 
* 
*  gcc -DDIRTY -Wall -o rawcopy rawcopy.c 
* 
*/ 
#include <fcntl.h> 
#include <sys/types.h> 
#include <sys/uio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#if defined(DIRTY) 
# if defined(BUFSIZ) 
#  error "Don't define your own BUFSIZ when DIRTY" 
# endif 
# include <stdio.h> 
# define PERROR perror(argv[0]) 
#else 
# define CLEAN 
# define PERROR 
# if ! defined(BUFSIZ) 
#  error "You must define your own BUFSIZ with -DBUFSIZ=<number>" 
# endif 
#endif 

char * buffer[BUFSIZ];   /* by definition stdio BUFSIZ should 
            be optimal size for read/write */ 

extern int errno ;    /* I/O errors */ 

int main(int argc, char * argv[]) { 
    int fdi, fdo ;    /* Input/output file descriptors */ 
    ssize_t len ;    /* length to read/write */ 
    if(argc != 3){ 
     PERROR; 
     exit(errno); 
    } 

    /* Open the files, returning perror errno as the exit value if fails. */ 
    if((fdi = open(argv[1],O_RDONLY)) == -1){ 
     PERROR; 
     exit(errno); 
    } 
    if((fdo = open(argv[2], O_WRONLY|O_CREAT)) == -1){ 
     PERROR; 
     exit(errno); 
    } 

    /* copy BUFSIZ bytes (or total read on last block) fast as you 
     can. */ 
    while((len = read(fdi, (void *) buffer, BUFSIZ)) > -1){ 
     if(len == -1){ 
      PERROR; 
      exit(errno); 
     } 
     if(write(fdo, (void*)buffer, len) == -1){ 
      PERROR; 
      exit(errno); 
     } 
    } 
    /* close and fsync the files */ 
    if(fsync(fdo) ==-1){ 
     PERROR; 
     exit(errno); 
    } 
    if(close(fdo) == -1){ 
     PERROR; 
     exit(errno); 
    } 
    if(close(fdi) == -1){ 
     PERROR; 
     exit(errno); 
    } 

    /* if it survived to here, all worked. */ 
    exit(0); 
} 
+0

IcedTea OpenJDK 1.6 Java, openSUSE 11 Linux, 4 core-CPU, 4 GB, 1 SATA-HD su FiberChannel da un JBOD. – dmeister

+0

Ho scritto un file da 4 GB utilizzando blocchi 64K degli stessi dati casuali e ho misurato il tempo tra il file aperto e il file close (e la sincronizzazione se è stata eseguita). – dmeister

+0

Qualsiasi altro carico di lavoro? Il C era con GCC> 4? Questa configurazione è simile a quella che ho provato con STK (RIP) e 120 MB/s, sembra piuttosto plausibile. –

0

Il codice C potrebbe essere suboptimale, poiché utilizza lo stdio anziché il raw OS write(). Ma poi, java potrebbe essere più ottimale perché assegna buffer più grandi?

Ad ogni modo, puoi fidarti solo dell'APIDOC. Il resto è al di là dei tuoi doveri.

6

In realtà, in C che si desidera chiamare solo fsync() sul descrittore di un file, non sync() (o il comando "sync"), che segnala al kernel di flush tutti i buffer a livello di sistema su disco.

Se strace (ottenendo specifico per Linux qui) la JVM si dovrebbe essere in grado di osservare una chiamata fsync() o fdatasync() sistema che viene fatta sul vostro file di output. Questo sarebbe quello che mi aspetterei dal getFD(). sync() chiamata da fare. Presumo che lo c.force(true) contrassegni semplicemente a NIO che fsync() deve essere chiamato dopo ogni scrittura. Potrebbe semplicemente essere che la JVM che stai utilizzando non implementa effettivamente la chiamata sync()?

Non sono sicuro del motivo per cui non si è vista alcuna differenza quando si chiama "sync" come comando: ma ovviamente, dopo la prima chiamata di sincronizzazione, quelli successivi sono in genere molto più veloci.Ancora una volta, sarei propenso a rompere strace (traliccio su Solaris) come "cosa sta realmente accadendo qui?" strumento.

+0

L'idea di tracciare le syscalls è buona. Lo farò domani. – dmeister

+0

force() chiama fsync o fdatasync (a seconda del flag dei metadati). Tuttavia, non imposta uno stato per chiamare fsync/fdatasync direttamente dopo ogni chiamata. Ho cercato nel codice sorgente di OpenJDK. – dmeister

0

(so che questa è una risposta molto tardi, ma mi sono imbattuto in questa discussione facendo una ricerca su Google, e questo è probabilmente come sei finito anche qui.)

La vostra chiamata sync() in Java su un singolo descrittore di file, quindi solo i buffer relativi a quel file vengono scaricati sul disco.

In C e in riga di comando, si chiama sync() sull'intero sistema operativo, quindi ogni buffer di file viene scaricato su disco, per tutto ciò che il proprio O/S sta facendo.

Per essere paragonabile, la chiamata C deve essere su syncfs (fp);

Dalla pagina man di Linux:

sync() causes all buffered modifications to file metadata and data to 
    be written to the underlying file systems. 

    syncfs() is like sync(), but synchronizes just the file system contain‐ 
    ing file referred to by the open file descriptor fd. 
+1

syncfs() non è migliore di sync(), entrambi sono errati. La chiamata fdatasync() è quella che utilizza java e quella che si desidera utilizzare in C. – eckes

2

E 'una buona idea usare il completamento integrità dei dati di I/O sincronizzato. Tuttavia il tuo campione C sta usando il metodo sbagliato. Si utilizza sync(), che viene utilizzato per sincronizzare l'intero sistema operativo.

Se si desidera scrivere i blocchi di quel singolo file su disco, è necessario utilizzare fsync(2) o fdatasync(2) in C. A proposito: quando si utilizza stdio tamponata in C (o di una BufferedOutputStream o qualche scrittore in Java) è necessario svuota entrambi prima di sincronizzarti.

La variante fdatasync() è un po 'più efficiente se il file non ha cambiato il nome o la dimensione dal momento della sincronizzazione. Ma potrebbe anche non persitare tutti i metadati. Se si desidera scrivere i propri sistemi di database sicuri per le transazioni, è necessario osservare altre cose (come fsyncing della directory principale).

Problemi correlati