2009-04-08 43 views
29

Cách dữ liệu được ghi vào một tệp thực sự được xóa/đồng bộ hóa với thiết bị khối bằng Java.Thực sự buộc đồng bộ/tuôn ra tệp trong Java

Tôi đã thử mã này với nio:.

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

Tôi cho rằng c.force (true) togehter với s.getFD() sync() nên là đủ vì doc cho force bang

Buộc mọi bản cập nhật cho tệp của kênh này được ghi vào thiết bị lưu trữ chứa tệp đó. Nếu tệp của kênh này nằm trên thiết bị lưu trữ cục bộ thì khi phương thức này trả về, đảm bảo rằng tất cả thay đổi được thực hiện cho tệp kể từ khi kênh này được tạo hoặc kể từ khi phương thức này được gọi lần cuối, sẽ được ghi vào thiết bị đó. Điều này rất hữu ích để đảm bảo rằng thông tin quan trọng không bị mất trong trường hợp xảy ra sự cố hệ thống.

Các tài liệu để sync trạng thái:

Force tất cả các bộ đệm hệ thống để đồng bộ hóa với các thiết bị cơ bản. Phương thức này trả về sau khi tất cả các dữ liệu đã sửa đổi và các thuộc tính của FileDescriptor này đã được ghi vào (các) thiết bị liên quan. Đặc biệt, nếu FileDescriptor này đề cập đến một phương tiện lưu trữ vật lý, chẳng hạn như một tệp trong hệ thống tệp, đồng bộ sẽ không trả lại cho đến khi tất cả các bộ đệm được sửa đổi trong bộ đệm được liên kết với FileDesecriptor này đã được ghi vào phương tiện vật lý. đồng bộ hóa có nghĩa là được sử dụng bởi mã yêu cầu lưu trữ vật lý (chẳng hạn như tệp) ở trạng thái đã biết.

Hai cuộc gọi này là đủ. Là nó? Tôi đoán là không.

Nền: Tôi thực hiện so sánh hiệu suất nhỏ (2 GB, viết tuần tự) bằng C/Java và phiên bản Java nhanh gấp hai lần phiên bản C và có thể nhanh hơn phần cứng (120 MB/s trên một HD duy nhất). Tôi cũng đã thử thực hiện đồng bộ hóa công cụ dòng lệnh với Runtime.getRuntime(). Exec ("sync") nhưng điều đó đã không thay đổi hành vi.

Mã C kết quả là 70 MB/s được (sử dụng các API cấp thấp (mở, viết, đóng) không thay đổi nhiều):

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

Nếu không có cuộc gọi cuối cùng để đồng bộ; Tôi nhận được các giá trị không thực tế (hơn 1 GB hay còn gọi là hiệu năng bộ nhớ chính).

Tại sao có sự khác biệt lớn giữa C và Java? Có hai possiblities: Tôi không đồng bộ dữ liệu một cách chính xác trong Java hoặc mã C là suboptimal vì một lý do nào đó.

Cập nhật: Tôi đã thực hiện strace chạy với "strace -cfT cmd". Dưới đây là kết quả:

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 (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 

Thời gian giá trị dường như là hệ thống thời gian duy nhất và do đó là khá vô nghĩa.

Cập nhật 2: Tôi đã chuyển sang máy chủ khác, khởi động lại và tôi sử dụng ext3 được định dạng mới. Bây giờ tôi chỉ nhận được 4% sự khác biệt giữa Java và C. Tôi chỉ đơn giản là không biết những gì đã đi sai. Đôi khi mọi thứ thật kỳ lạ. Tôi nên thử đo bằng hệ thống khác trước khi viết câu hỏi này. Lấy làm tiếc.

Cập nhật 3: Để tóm tắt câu trả lời:.

  • Sử dụng c.force (true) tiếp theo là s.getFD() sync() cho Java NIO và s.flush() và s.getFD() .sync() cho API luồng của Java. Đối với API cấp cao trong C, đừng quên đồng bộ hóa. Một fflush đã gửi dữ liệu đến hệ điều hành, nhưng không mang dữ liệu của bạn đến thiết bị khối.
  • Sử dụng vạch để phân tích các syscalls được thực hiện bằng lệnh
  • Cross kiểm tra kết quả của bạn trước khi đăng câu hỏi.

Cập nhật 4: Hãy lưu ý theo dõi sau question.

+0

Tôi thực sự muốn xem thông lượng chỉ sử dụng các chức năng của phần 2. –

+0

Bạn đang sử dụng gì cho BLOCK_SIZE? Nó có cùng kích thước với bộ đệm của bạn trong Java không? 512 sẽ rất tối ưu trong những ngày này. Bạn có thể muốn ít nhất 4096 (kích thước trang trên x86) hoặc có thể cao hơn. Tôi đã thấy những cải tiến có thể đo lường lên đến 32k trên một số máy. Oh, và tất nhiên nếu bộ đệm của bạn được liên kết trang, nó sẽ cung cấp cho hạt nhân nhiều chỗ hơn để tối ưu hóa. – aij

+0

Một vấn đề khác có thể là mã bạn đã đăng không sử dụng "API cấp thấp (mở, viết, đóng)". Nó đang sử dụng cấp cao hơn, API stdio di động (fopen, fwrite, fclose) sẽ thêm một lớp đệm bổ sung theo mặc định. Bạn đã tắt một cách rõ ràng một nơi nào đó bên ngoài mã bạn đã đăng? – aij

Trả lời

2

Bạn cần cho chúng tôi biết thêm về phần cứng và hệ điều hành, cũng là phiên bản Java cụ thể. Bạn đo lường thông lượng này như thế nào?

Bạn đúng rằng lực/đồng bộ sẽ buộc dữ liệu ra phương tiện vật lý.


Đây là phiên bản nguyên bản của bản sao. Biên dịch với gcc 4.0 trên máy Mac Intel, phải sạch sẽ.

/* 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 lõi CPU, 4 GB, 1 SATA-HD qua FiberChannel từ JBOD. – dmeister

+0

Tôi đã viết một tệp 4 GB sử dụng khối 64K của cùng một dữ liệu ngẫu nhiên và đo thời gian giữa tệp đang mở và tệp đóng (và đồng bộ hóa nếu nó được thực hiện). – dmeister

+0

Bất kỳ khối lượng công việc nào khác? C là với GCC> 4? Cấu hình đó tương tự như cấu hình tôi đã thử ở STK (RIP) và âm thanh 120 MB/s khá hợp lý. –

0

Mã C có thể tối ưu hóa, vì nó sử dụng định dạng stdio hơn là hệ điều hành thô write(). Nhưng sau đó, java có thể tối ưu hơn vì nó phân bổ bộ đệm lớn hơn?

Dù sao, bạn chỉ có thể tin tưởng APIDOC. Phần còn lại là ngoài nhiệm vụ của bạn.

6

Trên thực tế, trong C bạn muốn chỉ cần gọi fsync() trên mô tả một tập tin, không sync() (hoặc "đồng bộ" lệnh) mà các tín hiệu hạt nhân để flush tất cả các bộ đệm để toàn hệ thống đĩa.

Nếu bạn strace (nhận riêng cho Linux tại đây) JVM, bạn sẽ có thể quan sát cuộc gọi hệ thống fsync() hoặc fdatasync() đang được thực hiện trên tệp đầu ra của mình. Đó sẽ là những gì tôi mong đợi là getFD(). sync() gọi điện để thực hiện. Tôi giả sử c.force(true) chỉ đơn giản là cờ để NIO rằng fsync() nên được gọi là sau mỗi lần viết. Có thể đơn giản là JVM bạn đang sử dụng không thực sự thực hiện cuộc gọi sync()? Tôi không chắc chắn lý do tại sao bạn không thấy bất kỳ sự khác biệt nào khi gọi "đồng bộ hóa" làm lệnh: nhưng rõ ràng là sau lần gọi đồng bộ đầu tiên, các lệnh tiếp theo thường khá nhanh hơn rất nhiều.Một lần nữa, tôi muốn có xu hướng phá vỡ strace (giàn trên Solaris) như là một "những gì đang thực sự xảy ra ở đây?" dụng cụ.

+0

Ý tưởng truy tìm các syscalls là tốt. Tôi sẽ làm điều đó vào ngày mai. – dmeister

+0

force() gọi fsync hoặc fdatasync (tùy thuộc vào cờ siêu dữ liệu). Tuy nhiên, nó không đặt trạng thái gọi trực tiếp fsync/fdatasync sau mỗi cuộc gọi. Tôi tìm nó trong mã nguồn OpenJDK. – dmeister

0

(Tôi biết đây là một câu trả lời rất muộn, nhưng tôi chạy vào chủ đề này làm một tìm kiếm Google, và có lẽ đó là cách bạn kết thúc ở đây quá.)

đồng bộ gọi điện thoại của bạn() trong Java trên một đơn tập tin mô tả, do đó, chỉ có bộ đệm liên quan đến một tập tin được flushed ra đĩa.

Trong C và dòng lệnh, bạn đang gọi đồng bộ() trên toàn bộ hệ điều hành - vì vậy mọi bộ đệm tệp sẽ được chuyển sang đĩa, cho mọi thứ mà O/S của bạn đang thực hiện.

Để so sánh, cuộc gọi C phải là syncfs (fp);

Từ trang người đàn ông 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. 
+1

syncfs() không tốt hơn là đồng bộ hóa(), cả hai đều sai. Các fdatasync() gọi là một trong đó java sử dụng và một trong những bạn muốn sử dụng trong C. – eckes

2

Đó là một ý tưởng tốt để sử dụng I/O hoàn thành toàn vẹn dữ liệu đồng bộ. Tuy nhiên mẫu C của bạn đang sử dụng phương pháp sai. Bạn sử dụng sync(), được sử dụng để đồng bộ hóa toàn bộ hệ điều hành.

Nếu bạn muốn ghi các khối của tệp đó vào đĩa, bạn cần sử dụng fsync(2) hoặc fdatasync(2) trong C. BTW: khi bạn sử dụng bộ đệm đệm trong C (hoặc BufferedOutputStream hoặc Writer trong Java), bạn cần tuôn ra cả hai trước khi bạn đồng bộ hóa.

Biến thể fdatasync() hiệu quả hơn một chút nếu tệp không thay đổi tên hoặc kích thước kể từ khi bạn đồng bộ hóa. Nhưng nó cũng có thể không persit tất cả các dữ liệu meta. Nếu bạn muốn viết các hệ thống cơ sở dữ liệu an toàn giao dịch của riêng mình, bạn cần quan sát một số thứ khác (như fsyncing thư mục cha).

Các vấn đề liên quan