2009-04-08 15 views
29

W jaki sposób dane zapisane w pliku naprawdę są przepłukiwane/synchronizowane z urządzeniem blokowym przez Javę.Naprawdę wymuś synchronizację plików/flush w Javie

Próbowałem tego kodu z NIO.

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

mam ten c.force (true) togehter z s.getFD sync()() powinny być wystarczające, ponieważ doc dla force stanach

Wymusza aktualizację pliku tego kanału, aby zapisać go na urządzeniu pamięci, które go zawiera. Jeśli plik tego kanału znajduje się na lokalnym urządzeniu pamięci, to po zwróceniu tej metody jest gwarantowane, że wszystkie zmiany dokonane w tym pliku, ponieważ ten kanał został utworzony, lub od czasu ostatniego wywołania tej metody, zostaną zapisane na tym urządzeniu. Jest to przydatne do zapewnienia, że ​​krytyczne informacje nie zostaną utracone w przypadku awarii systemu.

dokumentacja do sync stany:

Siła wszystkie bufory systemowe Aby zsynchronizować z urządzeniem bazowym. Ta metoda zwraca wszystkie zmodyfikowane dane i atrybuty tego FileDescriptor zostały zapisane na odpowiednich urządzeniach. W szczególności, jeśli ten FileDescriptor odnosi się do fizycznego nośnika danych, takiego jak plik w systemie plików, synchronizacja nie powróci, dopóki wszystkie zmodyfikowane w pamięci kopie buforów skojarzonych z tym obiektem FileDesecriptor nie zostaną zapisane na nośniku fizycznym. Synchronizacja ma być używana przez kod, który wymaga fizycznego przechowywania (takiego jak plik), aby był w znanym stanie.

Te dwie rozmowy powinny wystarczyć. Czy to jest? Chyba nie są.

Tło: Wykonuję małe porównanie wydajności (2 GB, zapis sekwencyjny) przy użyciu C/Java, a wersja Java jest dwukrotnie szybsza niż wersja C i prawdopodobnie jest szybsza niż sprzęt (120 MB/s na jednym dysku HD). Próbowałem również wykonać synchronizację narzędzia wiersza poleceń z Runtime.getRuntime(). Exec ("sync"), ale to nie zmieniło zachowania.

Kod C w wyniku czego 70 MB/s (przy użyciu API niskim poziomie (otwarte, pisać, zamknij) nie zmienia się wiele):

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

Bez ostatecznego wezwania do synchronizacji; Mam nierealistyczne wartości (ponad 1 GB aka pamięci głównej).

Dlaczego między C a Javą jest tak duża różnica? Istnieją dwie możliwości: Nie synchronizuję danych poprawnie w Javie lub kod C jest z jakiegoś powodu nieoptymalny.

Aktualizacja: Wykonałem strace z "strace -cfT cmd". Oto wyniki:

C (Low-Level 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 (High-Level 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 (JRE 1.6 SUN, 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 

Czas wartości wydają się być tylko czas systemowy i dlatego są całkiem pozbawione sensu.

Aktualizacja 2: Przełączyłem się na inny serwer, zrestartowałem i używam świeżego sformatowanego ext3. Teraz dostaję tylko 4% różnice między Javą i C. Po prostu nie wiem, co poszło nie tak. Czasami rzeczy są dziwne. Powinienem był wypróbować pomiar w innym systemie przed napisaniem tego pytania. Przepraszam.

Update 3: Podsumowując odpowiedź.

  • Używaj c.force (true), a następnie s.getFD sync()() Java NIO i s.flush() i s.getFD() .sync() dla API strumienia Java. Dla interfejsu API wysokiego poziomu w C nie zapomnij zsynchronizować. A fflush przesłał dane do systemu operacyjnego, ale nie przyniósł danych do urządzenia blokowego.
  • Użyj strace, aby przeanalizować linie systemowe wykonane komendą
  • Sprawdź krzyżyk wyników przed opublikowaniem pytania.

Aktualizacja 4: Należy zwrócić uwagę na następującą kontynuację: question.

+0

Chciałbym zobaczyć przepustowość za pomocą tylko funkcji sekcji 2. –

+0

Czego używasz dla BLOCK_SIZE? Czy jest tego samego rozmiaru co twój bufor w Javie? 512 będzie w dzisiejszych czasach bardzo nieoptymalnych. Prawdopodobnie chcesz mieć co najmniej 4096 (rozmiar strony na x86) lub nawet więcej. Na niektórych komputerach zauważyłem wymierne poprawki do 32 tys. Aha, i oczywiście jeśli twój bufor jest wyrównany do strony, to da jądro więcej miejsca na optymalizację. – aij

+0

Innym możliwym problemem jest to, że opublikowany przez Ciebie kod nie używa "API niskiego poziomu (otwórz, napisz, zamknij)". Używa on wyższego poziomu, przenośnego interfejsu API stdio (fopen, fwrite, fclose), który domyślnie dodaje dodatkową warstwę buforowania. Czy jawnie wyłączyłeś buforowanie poza kodem, który opublikowałeś? – aij

Odpowiedz

2

Musisz nam powiedzieć więcej o sprzęcie i systemie operacyjnym, a także o konkretnej wersji Java. Jak mierzysz tę wydajność?

Masz rację, że wymuszenie/synchronizacja powinno wymusić przekazanie danych na nośnik fizyczny.


Oto surowa wersja kopii. Skompilowany z gcc 4.0 na Intel Mac, powinien być czysty.

/* 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

IcedTea OpenJDK 1.6 Java, openSUSE 11 Linux, 4-rdzeniowy-CPU, 4 GB, 1 SATA-HD przez FiberChannel z JBOD. – dmeister

+0

Napisałem plik 4 GB przy użyciu bloków 64-krotnie tych samych losowych danych i zmierzyłem czas pomiędzy otwarciem pliku a zamknięciem pliku (i zsynchronizowaniem, jeśli jest to zrobione). – dmeister

+0

Jakieś inne obciążenie pracą? C było z GCC> 4? Ta konfiguracja jest podobna do tej, którą wypróbowałem w STK (RIP) i 120 MB/s brzmi dość przekonująco. –

0

Kod C może być suboptymalny, ponieważ używa stdio zamiast surowego OS write(). Ale wtedy java może być bardziej optymalna, ponieważ alokuje większe bufory?

W każdym razie można ufać tylko APIDOC. Reszta wykracza poza twoje obowiązki.

6

Właściwie w C chcesz po prostu zadzwonić fsync() na deskryptorze pliku, a nie jeden sync() (lub polecenia „sync”), który sygnalizuje jądro flush wszystkie bufory dla całego systemu na dysku.

Jeśli otrzymasz strace (w tym przypadku specyficzne dla systemu Linux) JVM, powinieneś być w stanie zaobserwować wywołanie systemowe fsync() lub fdatasync() na twoim pliku wyjściowym. To właśnie bym oczekiwał getFD(). sync() zadzwoń do zrobienia. Zakładam, że c.force(true) po prostu flagi do NIO, które powinny być wywoływane fsync() po każdym zapisie. Może się tak zdarzyć, że JVM, z której korzystasz, w rzeczywistości nie wykonuje wywołania sync()?

Nie jestem pewien, dlaczego nie widzisz żadnej różnicy podczas wywoływania "sync" jako polecenia: ale oczywiście, po pierwszym wywołaniu synchronizacji, kolejne są zwykle o wiele szybsze.Znowu, byłbym skłonny wyrwać się z strace (wiązka na Solaris) jako "co się tutaj dzieje?" narzędzie.

+0

Pomysł śledzenia stron systemowych jest dobry. Zrobię to jutro. – dmeister

+0

force() wywołuje fsync lub fdatasync (w zależności od flagi metadanych). Nie ustawia jednak stanu wywoływania fsync/fdatasync bezpośrednio po każdym wywołaniu. Sprawdziłem to w kodzie źródłowym OpenJDK. – dmeister

0

(wiem, że jest to bardzo późno odpowiedź, ale wpadłem na tego wątku robi wyszukiwania Google, a to zapewne, jak skończyło się tu.)

synchronizacja wywołujący() w Javie na pojedynczym deskryptor pliku, więc tylko te bufory związane z tym jednym plikiem zostaną wypuszczone na dysk.

W C i linii poleceń wywołujesz funkcję sync() w całym systemie operacyjnym - dzięki czemu każdy bufor na pliki zostanie przepłukany na dysk, dla wszystkiego, co robi O/S.

Aby być porównywalnym, wywołanie C powinno być zsynchronizowane (fp);

Na stronie Linux man:

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. 
+1

syncfs() nie jest lepszy niż sync(), oba są niepoprawne. Wywołanie fdatasync() jest tym, którego używa java i którego chcesz użyć w C. – eckes

2

To jest dobry pomysł, aby użyć zsynchronizowany zakończenie integralności danych I/O. Jednak twoja próbka C używa niewłaściwej metody. Używasz sync(), który służy do synchronizacji całego systemu operacyjnego.

Jeśli chcesz napisać bloki tego jednego pliku na dysku, trzeba użyć fsync(2) lub fdatasync(2) w C. BTW: jeśli używasz buforowany stdio w C (lub BufferedOutputStream lub niektóre Writer w Javie) trzeba przepłukać oba przed rozpoczęciem synchronizacji.

Wariant fdatasync() jest nieco bardziej wydajny, jeśli plik nie zmieniał nazwy ani rozmiaru od momentu synchronizacji. Ale może również nie sprawdzić wszystkich metadanych. Jeśli chcesz napisać własne bezpieczne systemy baz danych transakcyjnych, musisz zaobserwować trochę więcej rzeczy (np. Fsyncing do katalogu nadrzędnego).

Powiązane problemy