Действительно принудительно синхронизировать / очистить файл в Java - PullRequest
35 голосов
/ 08 апреля 2009

Как данные, записанные в файл , действительно могут быть сброшены / синхронизированы с блочным устройством с помощью Java.

Я попробовал этот код с NIO:

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

Я предположил, что c.force (true) вместе с s.getFD (). Sync () должно быть достаточно, потому что документ для force сообщает

Принудительно записывает любые обновления файла этого канала на устройство хранения, которое его содержит. Если файл этого канала находится на локальном запоминающем устройстве, то при возврате этого метода гарантируется, что все изменения, внесенные в файл с момента создания этого канала или с момента последнего вызова этого метода, будут записаны на это устройство. Это полезно для гарантии того, что критическая информация не будет потеряна в случае сбоя системы.

Документация к sync гласит:

Принудительно синхронизировать все системные буферы с базовым устройством. Этот метод возвращает после того, как все измененные данные и атрибуты этого FileDescriptor были записаны на соответствующие устройства. В частности, если этот FileDescriptor ссылается на физический носитель данных, такой как файл в файловой системе, синхронизация не вернется, пока все измененные в памяти копии буферов, связанных с этим FileDesecriptor, не будут записаны на физический носитель. sync предназначен для использования кодом, который требует, чтобы физическое хранилище (например, файл) находилось в известном состоянии.

Этих двух вызовов должно быть достаточно. Это? Я думаю, что это не так.

Справочная информация: я делаю небольшое сравнение производительности (2 ГБ, последовательная запись) с использованием C / Java, а версия Java в два раза быстрее, чем версия C, и, вероятно, быстрее, чем аппаратная (120 МБ / с на одном HD) , Я также попытался выполнить синхронизацию инструмента командной строки с Runtime.getRuntime (). Exec ("sync"), но это не изменило поведение.

Код C, обеспечивающий скорость 70 МБ / с: (использование низкоуровневых API (открытие, запись, закрытие) мало что меняет):

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

без последнего вызова синхронизации; Я получил нереальные значения (более 1 ГБ, или производительность основной памяти).

Почему такая большая разница между C и Java? Есть две возможности: я неправильно синхронизирую данные в Java или код C по какой-то причине неоптимален.

Обновление: Я выполнил стрейс с помощью "strace -cfT cmd". Вот результаты:

C (API низкого уровня): МБ / с 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 высокого уровня): МБ / с 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): МБ / с 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): МБ / с 127,45830221558376

  5.52    0.980061      490031         2           fsync
  1.60    0.284752           9     32774           write
  0.00    0.000000           0        80           close

Кажется, что значения времени относятся только к системному времени и поэтому довольно бессмысленны.

Обновление 2: Я переключился на другой сервер, перезагрузился, и я использую новый отформатированный ext3. Теперь я получаю только 4% разницы между Java и C. Я просто не знаю, что пошло не так. Иногда вещи странные. Я должен был попробовать измерение с другой системой, прежде чем писать этот вопрос. К сожалению.

Обновление 3: Подведем итоги ответов:

  • Используйте c.force (true), а затем s.getFD (). Sync () для Java NIO и s.flush () и s.getFD (). Sync () для потокового API Java. Для API высокого уровня в C не забудьте синхронизировать. Fflush отправил данные в ОС, но не перенес их на блочное устройство.
  • Используйте strace для анализа системных вызовов, выполненных командой
  • Пересмотрите свои результаты перед отправкой вопроса.

Обновление 4: Пожалуйста, обратите внимание на следующее продолжение вопрос .

Ответы [ 5 ]

9 голосов
/ 08 апреля 2009

На самом деле, в C вы хотите просто вызвать fsync() для одного файлового дескриптора, а не sync() (или команду "sync"), которая сигнализирует ядру на flush все буферы на диск всей системы.

Если вы strace (в данном случае относитесь к Linux) к JVM, вы сможете наблюдать системный вызов fsync() или fdatasync(), выполняемый в вашем выходном файле. Это было бы тем, что я ожидал от звонка getFD(). sync(). Я предполагаю, что c.force(true) просто указывает NIO, что fsync() должен вызываться после каждой записи. Может просто случиться так, что используемая вами JVM на самом деле не реализует вызов sync()?

Я не уверен, почему вы не видели никакой разницы при вызове «sync» как команды: но очевидно, что после первого вызова sync последующие обычно намного быстрее. Снова, я был бы склонен выдвинуть strace (ферма на Солярисе) как "что фактически здесь происходит?" инструмент.

5 голосов
/ 24 мая 2014

Рекомендуется использовать завершение целостности синхронизированных данных ввода / вывода. Однако ваш образец C использует неправильный метод. Вы используете sync(), который используется для синхронизации всей ОС.

Если вы хотите записать блоки этого отдельного файла на диск, вам нужно использовать fsync(2) или fdatasync(2) в C. Кстати: когда вы используете буферизованный stdio в C (или BufferedOutputStream, или какой-либо Writer в Java) прежде чем выполнять синхронизацию, необходимо сначала очистить оба.

Вариант fdatasync() немного более эффективен, если файл не изменил имя или размер после синхронизации. Но это также может не сохранить все метаданные. Если вы хотите написать свои собственные транзакционно-безопасные системы баз данных, вам нужно учесть еще кое-что (например, fsyncing родительского каталога).

2 голосов
/ 08 апреля 2009

Вы должны рассказать нам больше об оборудовании и операционной системе, а также о конкретной версии Java. Как вы измеряете эту пропускную способность?

Вы правы, что принудительная синхронизация должна принудительно передавать данные на физический носитель.


Вот сырая версия копии. Скомпилированный с gcc 4.0 на Intel Mac, должен быть чистым.

/* 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 голосов
/ 02 апреля 2014

(я знаю, что это очень поздний ответ, но я наткнулся на эту тему, выполняя поиск в Google, и, вероятно, именно так вы и оказались здесь.)

Ваш вызов sync () в Java для одного файлового дескриптора, поэтому только те буферы, которые связаны с этим одним файлом, сбрасываются на диск.

В C и командной строке вы вызываете sync () для всей операционной системы - поэтому каждый файловый буфер сбрасывается на диск для всего, что делает ваш O / S.

Чтобы быть сопоставимым, вызов C должен быть syncfs (fp);

Со страницы руководства 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 голосов
/ 08 апреля 2009

Код C может быть неоптимальным, потому что он использует stdio, а не raw OS write (). Но тогда java может быть более оптимальным, поскольку он выделяет большие буферы?

В любом случае, вы можете доверять только APIDOC. Остальное не входит в ваши обязанности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...