Pregunta Realmente fuerza la sincronización / enjuague de archivos en Java


Cómo pueden los datos escritos en un archivo De Verdad ser enjuagado / sincronizado con el dispositivo de bloque por Java.

Intenté 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()

Supuse que c.force (verdadero) junto con s.getFD (). Sync () debería ser suficiente porque el documento de fuerza estados

Obliga a que cualquier actualización del archivo de este canal se escriba 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ó este método por última vez se hayan 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 para sincronización estados:

Fuerce todos los búferes del sistema para que se sincronicen 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 (usando 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 la llamada final 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.

Actualizar: He hecho strace runs con "strace -cfT cmd". Aquí están los resultados:

C (API de bajo nivel): MB / s 67.389782

% de segundos de uso de los segundos / llamada de llamadas errores syscall
------ ----------- ----------- --------- --------- ---- ------------
 87.21 0.200012 200012 1 fdatasync
 11.05 0.025345 1 32772 escribir
  1.74 0.004000 4000 1 sincronización

C (API de alto nivel): MB / s 61.796458

% de segundos de uso de los segundos / llamada de llamadas errores syscall
------ ----------- ----------- --------- --------- ---- ------------
 73.19 0.144009 144009 1 sincronización
 26.81 0.052739 1 65539 escribir

Java (1.6 SUN JRE, API java.io): MB / s 128.6755466197537

% de segundos de uso de los segundos / llamada de llamadas errores syscall
------ ----------- ----------- --------- --------- ---- ------------
 80.07 105.387609 3215 32776 escribir
  2.58 3.390060 3201 1059 leer
  0.62 0.815251 815251 1 fsync

Java (1.6 SUN JRE, java.nio API): MB / s 127.45830221558376

  5.52 0.980061 490031 2 fsync
  1.60 0.284752 9 32774 escribir
  0.00 0.000000 0 80 cerrar

Los valores de tiempo parecen ser solo del sistema y, por lo tanto, carecen de sentido.

Actualización 2: Cambié a otro servidor, reinicié mi funcionamiento y uso un ext3 nuevo formateado. 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:

  • Utilice c.force (true) 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.
  • Usa strace para analizar las llamadas de sys hechas por un comando
  • Revise sus resultados antes de publicar una pregunta.

Actualización 4: Tenga en cuenta el siguiente seguimiento pregunta.


32
2018-04-08 15:27


origen


Respuestas:


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

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


Aquí hay una versión sin procesar 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);
}

2
2018-04-08 15:47



En realidad, en C solo quieres llamar fsync() en el descriptor de un archivo, no sync() (o el comando "sync") que indica al kernel flush todos los búferes a disco en todo el sistema.

Si tu strace (consiguiendo Linux-specific aquí) la JVM debería poder observar un fsync() o fdatasync() Llamada al sistema realizada en su archivo de salida. Eso sería lo que esperaría getFD().sync() llama para hacer. Asumo c.force(true) simplemente banderas a NIO que fsync() debe llamarse después de cada escritura. Podría ser simplemente que la JVM que está utilizando no implementa realmente el sync() ¿llamada?

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, los siguientes suelen ser bastante más rápidos. De nuevo, me inclinaría a salir strace (truss en Solaris) como "¿qué está pasando realmente aquí?" herramienta.


8
2018-04-08 18:48



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

Si desea escribir los bloques de ese único archivo en el disco, debe usar fsync(2) o fdatasync(2) en C. A propósito: cuando usa stdio con búfer en C (o un BufferedOutputStream o algún escritor en Java) necesita enjuagar ambos antes de sincronizar.

los fdatasync() La variante 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).


3
2018-04-08 15:36



El código C podría ser subóptimo, ya que usa stdio en lugar de raw write (). 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.


0
2018-04-01 23:51



(Sé que esta es una respuesta tardía, pero me encontré con este hilo haciendo una búsqueda en Google, y es probable que también termine aquí).

Su llamada a sync () en Java en un único 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 man de 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.

0