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 关联的所有内存中修改的缓冲区副本都已写入物理介质之前,同步将不会返回。同步旨在供需要物理存储(例如文件)处于已知状态的代码使用。
这两个调用应该足够了。是吗?我猜他们不是。
背景:我使用 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 调用错误 syscall ------ ----------- ------------ --------- --------- ---- ------------ 87.21 0.200012 200012 1 fdatasync 11.05 0.025345 1 32772 写 1.74 0.004000 4000 1 同步
C(高级 API):MB/s 61.796458
% time seconds usecs/call 调用错误 syscall ------ ----------- ------------ --------- --------- ---- ------------ 73.19 0.144009 144009 1 同步 26.81 0.052739 1 65539 写
Java(1.6 SUN JRE、java.io API):MB/s 128.6755466197537
% time seconds 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、java.nio API):MB/s 127.45830221558376
5.52 0.980061 490031 2 同步 1.60 0.284752 9 32774 写入 0.00 0.000000 0 80 关闭
时间值似乎只是系统时间,因此毫无意义。
更新 2:我切换到另一台服务器,重新启动,并使用全新格式化的 ext3。现在,Java 和 C 之间只有 4% 的差异。我根本不知道出了什么问题。有时事情很奇怪。在写这个问题之前,我应该用另一个系统尝试测量。对不起。
更新 3:总结答案:
- 使用 c.force(true) 后跟 s.getFD().sync() 用于 Java NIO 和 s.flush() 和 s.getFD().sync() 用于 Java 的流 API。对于 C 中的高级 API,不要忘记同步。fflush 将数据提交给操作系统,但不会将您的数据带到块设备。
- 使用 strace 分析命令完成的系统调用
- 在发布问题之前交叉检查您的结果。
更新 4:请注意以下后续问题。