如何通过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。 sync意味着需要物理存储(例如文件)处于已知状态的代码。
这两个电话应该足够了。是吗?我猜他们不是。
背景:我使用C / Java进行小的性能比较(2 GB,顺序写入),Java版本的速度是C版本的两倍,可能比硬件速度快(单个HD上的速度为120 MB / s) 。我还尝试使用Runtime.getRuntime()。exec(“sync”)执行命令行工具同步,但这并没有改变行为。
导致70 MB / s的C代码(使用低级API(打开,写入,关闭)变化不大):
FILE* fp = fopen(filename, "w");
while(xyz) {
fwrite(buffer, 1, BLOCK_SIZE, fp);
}
fflush(fp);
fclose(fp);
sync();
没有最后的同步调用;我得到了不切实际的价值(超过1 GB又称主内存性能)。
为什么C和Java之间有这么大的差异?有两种可能性:我没有在Java中正确同步数据,或者由于某种原因C代码是次优的。
更新: 我用“strace -cfT cmd”完成了strace运行。结果如下:
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。现在我在Java和C之间只有4%的差异。我只是不知道出了什么问题。有时事情很奇怪。在写这个问题之前,我应该尝试用另一个系统进行测量。遗憾。
更新3: 总结答案:
更新4: 请注意以下后续行动question。
答案 0 :(得分:9)
实际上,在C中你只想在一个文件描述符上调用fsync()
,而不是sync()
(或“sync”命令),它向内核发送信号flush
所有缓冲区为磁盘系统范围。
如果您strace
(在此处获取特定于Linux的)JVM,您应该能够观察到对输出文件进行的fsync()
或fdatasync()
系统调用。这就是我期望getFD()
。sync()
调用的内容。我假设c.force(true)
只是向NIO标记每次写入后应该调用fsync()
。可能只是您正在使用的JVM实际上没有实现sync()
调用?
我不确定为什么在将“sync”作为命令调用时没有看到任何区别:但显然,在第一次同步调用之后,后续的通常要快得多。再一次,我倾向于将strace
(Solaris上的桁架)打破为“这里究竟发生了什么?”工具。
答案 1 :(得分:5)
使用同步I / O数据完整性完成是个好主意。但是,您的C示例使用了错误的方法。您使用sync()
,它用于同步整个操作系统。
如果要将该单个文件的块写入磁盘,则需要在C中使用fsync(2)
或fdatasync(2)
。顺便说一下:当您在C中使用缓冲的stdio时(或者在BufferedOutputStream中使用Java中的Writer)在同步之前需要先刷新它们。
如果文件在您同步后没有更改名称或大小,则fdatasync()
变体会更有效。但它也可能不会持久存在所有元数据。如果你想编写自己的事务安全数据库系统,你需要观察更多的东西(比如fsyncing父目录)。
答案 2 :(得分:2)
您需要告诉我们有关硬件和操作系统的更多信息,以及特定的Java版本。你是如何衡量这个吞吐量的?
你是正确的,强制/同步应该强制数据输出到物理媒体。
这是副本的原始版本。在Intel Mac上用gcc 4.0编译,应该是干净的。
/* 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);
}
答案 3 :(得分:0)
C代码可能不是最理想的,因为它使用stdio而不是原始OS write()。但是,java可能更优,因为它分配了更大的缓冲区?
无论如何,您只能信任APIDOC。其余的超出了你的职责。
答案 4 :(得分:0)
(我知道这是一个非常晚的回复,但我在这个帖子中遇到谷歌搜索,这可能也是你在这里结束的。)
您在单个文件描述符上使用Java调用sync(),因此只有与该文件相关的缓冲区才会刷新到磁盘。
在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.