Подтвердить что ты не робот

На самом деле принудительная синхронизация файлов/флеш в 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) togehter с s.getFD(). sync() должно быть достаточным, потому что doc для force состояния

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

В документации к sync указано:

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

Эти два вызова должны быть достаточными. Это? Наверное, это не так.

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

Код C, приводящий к 70 Мбайт/с (с использованием API низкого уровня (open, write, close), не сильно меняется):

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 низкого уровня): 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 высокого уровня): 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

Значения времени представляют собой только системное время и, следовательно, довольно бессмысленны.

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

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

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

Обновление 4: Обратите внимание на следующие действия question.

4b9b3361

Ответ 1

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

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


Здесь исходная версия копии. Компиляция с 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);
}

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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