2009-04-08 12 views
29

¿Cómo se pueden borrar los datos escritos en un archivo realmente con Java/sincronizado con el dispositivo de bloques?Realmente fuerza la sincronización/enrasado de archivos en Java

yo probamos este código con NIO:.

FileOutputStream s = new FileOutputStream(filename) 
Channel c = s.getChannel() 
while(xyz) 
    c.write(buffer) 
c.force(true) 
s.getFD().sync() 
c.close() 

supone que c.force (verdadero) togehter de la s.getFD() sync() debe ser suficiente debido a que el documento de force estados

Obliga a cualquier actualización del archivo de este canal a escribirse en el dispositivo de almacenamiento que lo contiene. Si el archivo de este canal reside en un dispositivo de almacenamiento local, cuando este método retorne, se garantiza que todos los cambios realizados en el archivo desde que se creó este canal o desde que se invocó por última vez, se habrán escrito en ese dispositivo. Esto es útil para garantizar que la información crítica no se pierda en el caso de un bloqueo del sistema.

La documentación a sync estados:

Fuerza de todos los buffers del sistema para sincronizar con el dispositivo subyacente. Este método retorna después de que todos los datos y atributos modificados de este FileDescriptor se hayan escrito en los dispositivos correspondientes. En particular, si este FileDescriptor se refiere a un medio de almacenamiento físico, como un archivo en un sistema de archivos, la sincronización no regresará hasta que todas las copias modificadas en memoria de los búferes asociados con este FileDesecriptor se hayan escrito en el medio físico. la sincronización está destinada a ser utilizada por código que requiere almacenamiento físico (como un archivo) para estar en un estado conocido.

Estas dos llamadas deberían ser suficientes. ¿Lo es? Supongo que no lo son.

Antecedentes: hago una pequeña comparación de rendimiento (2 GB, escritura secuencial) usando C/Java y la versión de Java es dos veces más rápida que la versión C y probablemente más rápida que el hardware (120 MB/s en una sola HD) También traté de ejecutar la sincronización de la herramienta de línea de comandos con Runtime.getRuntime() .exec ("sincronización") pero eso no ha cambiado el comportamiento.

El código C que resulta en 70 MB/s es (utilizando las API de bajo nivel (abrir, escribir, cerrar) no cambia mucho):

FILE* fp = fopen(filename, "w"); 
while(xyz) { 
    fwrite(buffer, 1, BLOCK_SIZE, fp); 
} 
fflush(fp); 
fclose(fp); 
sync(); 

Sin el último momento para sincronizar; Obtuve valores poco realistas (más de 1 GB, también conocido como rendimiento de la memoria principal).

¿Por qué hay una gran diferencia entre C y Java? Hay dos posibilidades: no sincronizo los datos correctamente en Java o el código C es subóptimo por alguna razón.

Actualización: He hecho carreras de poca altura con "strace -cfT cmd". Aquí están los resultados:

C (API de bajo nivel): 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 (API de alto nivel): 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 

de Java (JRE SUN 1.6, 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 

El tiempo de los valores parecen ser sólo la hora del sistema y por lo tanto son bastante sentido.

Actualización 2: Cambié a otro servidor, reinicié mi funcionamiento y utilizo un ext3 formateado nuevo. Ahora obtengo solo un 4% de diferencias entre Java y C. Simplemente no sé qué salió mal. A veces las cosas son extrañas. Debería haber intentado la medición con otro sistema antes de escribir esta pregunta. Lo siento.

Actualización 3: Para resumir las respuestas:.

  • Uso c.force (verdadero), seguido de s.getFD() sync() para Java NIO y s.flush() y s.getFD() .sync() para la API de transmisión de Java. Para la API de alto nivel en C, no te olvides de sincronizar. A fflush envió los datos al sistema operativo, pero no trae sus datos al dispositivo de bloque.
  • Utilice strace para analizar las llamadas realizadas por un comando
  • Revise los resultados antes de publicar una pregunta.

Actualización 4: Tenga en cuenta lo siguiente seguimiento question.

+0

Realmente me gustaría ver el rendimiento utilizando solo las funciones de la sección 2. –

+0

¿Qué estás usando para BLOCK_SIZE? ¿Es del mismo tamaño que tu buffer en Java? 512 va a ser muy inferior al óptimo en estos días. Probablemente quieras al menos 4096 (tamaño de página en x86) o posiblemente más alto. He visto mejoras mensurables de hasta 32k en algunas máquinas. Ah, y por supuesto si su buffer está alineado con la página, le dará más espacio para la optimización. – aij

+0

Otro posible problema es que el código que publicaste no utiliza las "API de bajo nivel (abrir, escribir, cerrar)". Está utilizando la API stdio portátil de mayor nivel (fopen, fwrite, fclose) que agregará una capa adicional de almacenamiento en memoria intermedia de forma predeterminada. ¿Ha desactivado explícitamente el almacenamiento en búfer en algún lugar fuera del código que publicó? – aij

Respuesta

2

Es necesario que nos diga más sobre el hardware y sistema operativo, también la versión específica de Java. ¿Cómo estás midiendo este rendimiento?

Tiene la razón de que forzar/sincronizar debe forzar la salida de datos a los medios físicos.


Aquí hay una versión en bruto de la copia. Compilado con gcc 4.0 en una Mac Intel, debe estar limpio.

/* 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 sobre FibreChannel desde un JBOD. – dmeister

+0

Escribí un archivo de 4 GB usando 64K bloques de los mismos datos aleatorios y medí el tiempo entre el archivo abierto y el archivo cerrado (y sincronizo si está hecho). – dmeister

+0

¿Alguna otra carga de trabajo? La C estaba con GCC> 4? Esa configuración es similar a la que he probado en STK (RIP) y 120 MB/s suena bastante plausible. –

0

El código C podría ser subóptimo, ya que utiliza stdio en lugar de raw write() del sistema operativo. Pero entonces, ¿Java podría ser más óptimo porque asigna buffers más grandes?

De todos modos, solo puedes confiar en el APIDOC. El resto está más allá de tus deberes.

6

En realidad, en C sólo quiere llamar fsync() en el descriptor de un archivo, no sync() (o el comando "sync") que indica que el núcleo flush todos los tampones a todo el sistema de disco.

Si strace (conseguir específica de Linux aquí) la JVM debe ser capaz de observar una llamada fsync() o fdatasync() sistema que está siendo hecho en el archivo de salida. Eso sería lo que esperaría el getFD(). sync() llamada a hacer. Supongo c.force(true) simplemente indica a NIO que se debe llamar a fsync() después de cada escritura. ¿Podría ser simplemente que la JVM que está utilizando no implementa realmente la llamada sync()?

No estoy seguro de por qué no notó ninguna diferencia al llamar a "sincronización" como un comando: pero, obviamente, después de la primera invocación de sincronización, las siguientes suelen ser mucho más rápidas.Una vez más, me inclinaría a romper strace (truss en Solaris) como un "¿Qué está pasando realmente aquí?" herramienta.

+0

La idea de rastrear los syscalls es buena. Lo haré mañana. – dmeister

+0

force() llama a fsync o fdatasync (según el indicador de metadatos). Sin embargo, no establece un estado para llamar a fsync/fdatasync directamente después de cada llamada. Lo busqué en el código fuente de OpenJDK. – dmeister

0

(sé que esto es una respuesta muy tarde, pero me encontré con este tema haciendo una búsqueda en Google, y eso es probablemente la forma en que terminó aquí también.)

Su sincronización de llamada() en Java en una sola descriptor de archivo, por lo que solo los búferes relacionados con ese archivo se eliminan en el disco.

En C y en la línea de comandos, está llamando a sync() en todo el sistema operativo, por lo que cada búfer de archivo se vacía en el disco, por todo lo que hace su O/S.

Para ser comparable, la llamada C debe ser syncfs (fp);

Desde la página del 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() no es mejor que sync(), ambos son incorrectos. La llamada a fdatasync() es la que usa Java y la que desea usar en C. – eckes

2

Es una buena idea utilizar la E/S finalización integridad de los datos sincronizados. Sin embargo, su muestra C está utilizando el método incorrecto. Utiliza sync(), que se utiliza para sincronizar todo el sistema operativo.

Si desea escribir los bloques de ese único archivo en el disco, es necesario utilizar fsync(2) o fdatasync(2) en C. Por cierto: cuando se utiliza stdio tamponada en C (o un BufferedOutputStream o algún escritor en Java) que necesita para enjuague ambos primero antes de sincronizar.

La variante fdatasync() es un poco más eficiente si el archivo no ha cambiado el nombre o el tamaño desde la sincronización. Pero también puede que no persista todos los metadatos. Si desea escribir sus propios sistemas transaccionales de bases de datos seguras, necesita observar algunas cosas más (como sincronizar el directorio padre).

Cuestiones relacionadas