Действительно заставить файл синхронизации / сброса в Java

Как данные, записанные в файл, действительно могут быть сброшены / синхронизированы с блочным устройством с помощью 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() должно быть достаточно, потому что документ для принудительных состояний

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

Документация для синхронизации состояний:

Принудительно синхронизировать все системные буферы с базовым устройством. Этот метод возвращает после того, как все измененные данные и атрибуты этого 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 с помощью "strace -cfT cmd". Вот результаты:

C (API низкого уровня): МБ / с 67,389782

% time секунд usecs/call звонки ошибки syscall
------ ----------- ----------- --------- --------- ----------------
 87.21    0.200012      200012         1           fdatasync
 11,05    0,025345           1     32772 написать
  1,74    0,004000        4000         1 синхронизация

C (API высокого уровня): МБ / с 61,796458

% time секунд usecs/call звонки ошибки syscall
------ ----------- ----------- --------- --------- ----------------
 73,19    0,144009      144009         1 синхронизация
 26,81    0,052739           1       65539 написать

Java (1.6 SUN JRE, API java.io): МБ / с 128.6755466197537

% time секунд usecs/call звонки ошибки syscall
------ ----------- ----------- --------- --------- ----------------
 80,07  105,387609        3215     32776 написать
  2,58    3,390060        3201      1059 читать
  0,62    0,815251      815251         1 фсинк

Java (1.6 SUN JRE, API java.nio): МБ / с 127.45830221558376

  5,52    0,980061      490031         2           fsync
  1,60    0,284752           9     32774 написать
  0,00 0,000000           0        80 закрыть

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

Обновление 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 ответов

Решение

Вы должны рассказать нам больше об оборудовании и операционной системе, а также о конкретной версии 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);
}

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

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

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

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

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

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

(Я знаю, что это очень поздний ответ, но я наткнулся на эту тему, выполняя поиск в 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.

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

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

Другие вопросы по тегам