Vraiment forcer la synchronisation des fichiers / flush en Java

comment les données écrites dans un fichier really peuvent-elles être rincées/synchronisées avec le périphérique block par Java.

j'ai essayé ce code avec NIO:

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

je suppose que C. force (true) togehter with S. getfd ().sync () devrait être suffisant car le doc pour force states

oblige toute mise à jour du fichier de ce canal à être écrite sur le périphérique de stockage qui le contient. Si le fichier de ce canal se trouve sur un périphérique de stockage local puis lorsque cette méthode retourne, il est garanti que toutes les modifications apportées au fichier depuis que ce canal a été créé, ou depuis que cette méthode a été invoquée pour la dernière fois, aura été écrite sur ce périphérique. C'est utile pour s'assurer que l'information critique n'est pas perdu en cas de panne du système.

La documentation sync : le

forcer tous les tampons du système à synchronisez avec le dispositif sous-jacent. Cette méthode retourne après toutes les données modifiées et les attributs de cette FileDescriptor ont été écrites sur l'appareil concerné(s). En particulier, si ce FileDescriptor se réfère à un support de stockage physique, tel qu'un fichier dans un système de fichiers, sync ne retournera pas tant que toutes les copies modifiées en mémoire des tampons associés à ce Filedescriptor n'auront pas été écrites sur le support physique. sync est destiné à être utilisé par le code qui nécessite un stockage physique (comme un fichier) pour être dans un état connu.

ces deux appels devraient suffire. S'agit-il? Je suppose que non.

Background: je fais une petite comparaison de performances (2 Go, écriture séquentielle) en utilisant C/Java et la version Java est deux fois plus rapide que la version C et probablement plus rapide que le matériel (120 Mo/s sur un seul HD). J'ai aussi essayé d'exécuter l'outil en ligne de commande de synchronisation avec l'Exécution.getRuntime ().exec ("sync") mais cela n'a pas changé le comportement.

le code C résultant en 70 MB / s est (en utilisant les API de bas niveau (open, write, close) ne change pas beaucoup):

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

sans le dernier appel à la synchronisation; j'ai obtenu des valeurs irréalisables (plus de 1 Go alias mémoire principale performance).

Pourquoi y a-t-il une telle différence entre C et Java? Il y a deux possibilités: Je ne synchronise pas les données correctement en Java ou le code C est sous-optimal pour une raison quelconque.

mise à jour: J'ai fait strace fonctionne avec "strace-cfT cmd". Voici les résultats:

C (API de Bas Niveau): 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 Haut Niveau): 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, java.IO API): 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, java.NIO API): 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

les valeurs de temps semblent être Système Temps seulement et sont par conséquent, inutiles.

mise à jour 2: Je suis passé à un autre serveur, redémarré, et j'utilise un nouveau formaté ext3. Maintenant, je n'obtiens que 4% de différence entre Java et C. Je ne sais tout simplement pas ce qui a mal tourné. Parfois, les choses sont étranges. J'aurais dû essayer la mesure avec un autre système avant d'écrire cette question. Désolé.

mise à jour 3: Pour résumer les réponses:

  • utiliser c. force (true) suivie de s.getFD().sync () for Java NIO and S. flush() et S. getfd ().sync () pour L'API de flux de Java. Pour L'API de haut niveau en C, n'oubliez pas de synchroniser. Un fflush a soumis les données à L'OS, mais n'apporte pas vos données à l'appareil de bloc.
  • utilisez strace pour analyser les appels de système effectués par une commande
  • vérifiez vos résultats avant de poster une question.

mise à jour 4: Veuillez prendre note du suivi suivant question .

32
demandé sur Community 2009-04-08 19:27:57

5 réponses

vous DEVEZ nous en dire plus sur le matériel et le système d'exploitation, ainsi que sur la version Java spécifique. Comment êtes-vous en mesure de ce débit?

vous avez raison que force/sync devrait forcer les données vers le support physique.


Voici une version brute de la copie. Compilé avec gcc 4.0 sur un Mac Intel, devrait être propre.

/* 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
répondu Charlie Martin 2009-04-09 17:41:05

en fait, en C vous voulez juste appeler fsync() sur le descripteur de fichier unique, pas sync() (ou la commande" sync") qui signale le noyau à flush tous les tampons sur le disque à l'échelle du système.

si vous strace (en fonction de Linux ici) la JVM, vous devriez être en mesure d'observer un appel système fsync() ou fdatasync() sur votre fichier de sortie. C'est ce que j'attendais du getFD() . sync() appel à faire. Je suppose c.force(true) signale simplement à NIO que fsync() doit être appelé après chaque écriture. Il se pourrait tout simplement que la JVM que vous utilisez n'implémente pas l'appel sync() ?

Je ne suis pas sûr pourquoi vous ne voyiez aucune différence en appelant" sync " comme une commande: mais évidemment, après la première invocation sync, les suivantes sont généralement beaucoup plus rapides. Encore une fois, je serais enclin à faire ressortir strace (truss sur Solaris) comme un " ce qui se passe réellement ici?" outil.

8
répondu araqnid 2015-06-15 05:55:03

c'est une bonne idée d'utiliser l'intégrité synchronisée des données d'entrée/sortie. Cependant, votre échantillon C utilise la mauvaise méthode. Vous utilisez sync() , qui est utilisé pour synchroniser l'ensemble du système D'exploitation.

si vous voulez écrire les blocs de ce fichier unique sur le disque, vous devez utiliser fsync(2) ou fdatasync(2) dans C. BTW: lorsque vous utilisez stdio tamponné en C (ou un BufferedOutputStream ou un écrivain en Java), vous devez d'abord vider les deux avant de synchroniser.

La variante fdatasync() est un peu plus efficace si le fichier n'a pas changé de nom ou de taille depuis votre synchronisation. Mais cela pourrait aussi ne pas altérer toutes les méta-données. Si vous voulez écrire vos propres systèmes de base de données transactionnels sûrs, vous devez observer plus de choses (comme fsyncing le répertoire parent).

3
répondu eckes 2014-05-23 20:22:23

le code C pourrait être sous-optimal, car il utilise stdio plutôt que raw OS write(). Mais alors, java pourrait être plus optimal parce qu'il alloue de plus grands tampons?

de toute façon, vous ne pouvez faire confiance qu'à L'APIDOC. Le reste est au-delà de vos fonctions.

0
répondu Ingo 2009-04-08 15:36:36

(je sais que c'est une réponse très tardive, mais je suis tombé sur ce fil en faisant une recherche Google, et c'est probablement comme ça que vous avez fini ici aussi.)

votre appel sync () en Java sur un seul descripteur de fichier, de sorte que seuls les buffers relatifs à ce fichier sont évacués vers le disque.

en C et en ligne de commande, vous appelez sync() sur l'ensemble du système d'exploitation-donc chaque tampon de fichiers est envoyé sur le disque, pour tout ce que vos O/s font.

pour être comparable, l'appel C doit être syncfs (fp);

de la page de manuel 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
répondu kernwig 2014-04-01 23:51:01