繁体   English   中英

强制 JVM 在没有页面缓存的情况下执行所有 IO(例如 O_DIRECT)

[英]Force JVM to do all IO without page cache (e.g. O_DIRECT)

我正在对用 Java 编写的应用程序进行一些基准测试。 实验很重要,结果不受页面缓存的影响(我用的是linux)

因此,避免页面缓存的最佳方法是在打开文件时使用 O_DIRECT。 因此,我更改了 jre 源代码中的相应代码。

我的方法非常适用于通过FileOutputStream的所有内容(例如写入),但不适用于FileInputStream (例如读取)。

将 O_DIRECT 添加到FileInputStream的 open-call 时,JVM 无法加载任何类:

Error: Could not find or load main class perf.TestDirectIO

这个错误不是类路径问题,因为我可以通过使用“未破解的”JVM 来修复它。

所以打开文件似乎有问题。

对于有关如何解决此问题的任何建议,我感到非常高兴。

如果有人想做类似的事情,我已经在我的博客中记录了整个 hack


作为参考,这些是我对 JVM 代码所做的更改:

jdk/src/share/native/java/io/FileInputStream.c

 @@ -58,7 +60,8 @@
 JNIEXPORT void JNICALL
 Java_java_io_FileInputStream_open(JNIEnv *env, jobject this, jstring path) {
-    fileOpen(env, this, path, fis_fd, O_RDONLY);
+    fileOpen(env, this, path, fis_fd, O_RDONLY | O_DIRECT); // this is the change that causes all the problems
 }

此更改有效: jdk/src/solaris/native/java/io/FileOutputStream_md.c

@@ -55,8 +55,10 @@
 JNIEXPORT void JNICALL
 Java_java_io_FileOutputStream_open(JNIEnv *env, jobject this,
                                    jstring path, jboolean append) {
     fileOpen(env, this, path, fos_fd,
-             O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC));
+             O_WRONLY | O_DIRECT | O_CREAT | (append ? O_APPEND : O_TRUNC));
 }

我还更改了热点 jre 以确保 memory 对齐(这是 O_DIRECT 的要求) hotspot/src/share/vm/runtime/os.cpp

+# include <mm_malloc.h>
...
-  u_char* ptr = (u_char*)::malloc(size + space_before + space_after);
+  u_char* ptr = (u_char*)::_mm_malloc(size + space_before + space_after,512);

老帖子,但我最近写了一个名为Jaydio的小型库,希望能解决这个问题。 也许你会发现它很有用。

  "The thing that has always disturbed me about O_DIRECT is that the whole interface is just stupid, and was probably designed by a deranged monkey on some serious mind-controlling substances [*]." 

[*]换句话说,它是一个Oracleism。

- 来自Transmeta的Linus Torvalds,2002年5月11日

检查man 2 open NOTES部分:

O_DIRECT

O_DIRECT标志可能会对用户空间缓冲区的长度和地址以及I / O的文件偏移量施加对齐限制 在Linux中,对齐限制因文件系统和内核版本而异....

在Linux 2.4下,传输大小,用户缓冲区和文件偏移的对齐必须都是文件系统逻辑块大小的倍数。 在Linux 2.6下,对齐到512字节边界就足够了。 ....

总之,O_DIRECT是一个潜在的强大工具,应谨慎使用。 建议应用程序将O_DIRECT用作性能选项,默认情况下禁用该选项。

我认为,在JRE(类加载器)中有一些FileInputStream用法,其读取的偏移量或大小未对齐到512字节。 (对于高级格式 ,最小对齐可能更大,甚至4096字节,或一个4K页面。)

内核对于未对齐偏移的行为是灰色区域,一些信息在这里: RFC:澄清直接I / O语义,Theodore Ts'o,tytso @ mit,LWN,2009

其他有趣的讨论在这里: Linux:使用O_DIRECT访问文件 (kerneltrap,2007)

嗯,当DIRECT出现故障时,似乎应该回退到缓冲的I / O. 使用DIRECT的所有IO操作都是同步的。 可能是一些DMA效应? 或者O_DIRECTmmap组合?

更新:

感谢strace输出。 这是错误( grep O_DIRECT ,然后检查文件描述符操作):

28290 open("...pact/perf/TestDirectIO.class", O_RDONLY|O_DIRECT) = 11
28290 fstat(11, {st_mode=S_IFREG|0644, st_size=2340, ...}) = 0
28290 fcntl(11, F_GETFD)                = 0
28290 fcntl(11, F_SETFD, FD_CLOEXEC)    = 0
...skip
28290 stat("...pact/perf/TestDirectIO.class", {st_mode=S_IFREG|0644, st_size=2340, ...}) = 0
...skip
28290 read(11, "\312\376\272\276\0\0\0003\0\215\n\0-\0D\t\0E\0F\7\0G\n\0\3\0D\10\0H\n"..., 1024) = 1024
28290 read(11, 0x7f1d76d23a00, 1316)    = -1 EINVAL (Invalid argument)

未对齐的读取大小会导致EINVAL错误。 您的类文件长度为2340字节,它是1024 + 1316字节,未对齐。

您可以在Java下使用O_DIRECT,利用Java Native Access(JNA) 此处提供启用O_DIRECT的InputStream和OutputStream的实现。

只是为了更新,因为如果你谷歌O_DIRECT JVM ,这篇文章是第一个出现的:

使用O_DIRECT进行 Direct I/O 的能力在 2017 年被合并到 JDK 中:

创建 FileChannel 时,您可以传递ExtendedOpenOption.DIRECT

import com.sun.nio.file.ExtendedOpenOption;

import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;

FileChannel fc = FileChannel.open(f.toPath(), StandardOpenOption.WRITE, 
                                  ExtendedOpenOption.DIRECT);

在 2022 年,还可以考虑将新的 Foreign Memory API 与MemorySegment用于后备缓冲区。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM