强制JVM执行所有IO,而不使用页面缓存(例如O_DIRECT)

sr4lhrrt  于 2022-11-07  发布在  其他
关注(0)|答案(4)|浏览(136)

我正在对一个用Java编写的应用程序进行一些基准测试。对于实验来说,结果不受页面缓存的影响是非常重要的(我使用的是Linux)
因此,避免页面缓存的最佳方法是在打开文件时使用O_DIRECT。因此,我在jre的源代码中修改了相应的代码。
我的方法对于通过FileOutputStream的所有事情都非常有效(例如,写),但是对于FileInputStream(例如,阅读)就不起作用。
当将O_DIRECT添加到FileInputStream的开放调用时,JVM无法加载任何类:

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

这个错误不是类路径问题,因为我可以通过使用一个“未被攻击的”JVM来修复它。
因此,打开文件时似乎出现了问题。
我很高兴听到任何关于如何解决这个问题的建议。
如果有人想做类似的事情,我有documented the whole hack in my blog
作为参考,以下是我对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以确保内存对齐(这是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);
qcbq4gxm

qcbq4gxm1#

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

0qx6xfy6

0qx6xfy62#

"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  [*]."

[*]换句话说,这是一个甲骨文。
--Linus Torvalds from Transmeta, 11 May 2002中的一个
检查man 2 open的“注解”部分:
O_直接
O_DIRECT标志可能会对用户空间缓冲区的长度和地址 * 以及I/O的 * 文件偏移量 * 施加对齐 * 限制。在Linux中,对齐限制因文件系统和内核版本而异......
在Linux 2.4下,传输大小、用户缓冲区和文件偏移量的对齐都必须是文件系统的逻辑块大小的倍数。在Linux 2.6下,对齐512字节边界就足够了。......
总而言之,O_DIRECT是一个潜在的强大工具,应谨慎使用。建议应用程序将O_DIRECT的使用视为一个性能选项,默认情况下禁用该选项。
我认为,在JRE(类加载器)中有一些FileInputStream的用法,它的读取偏移量或大小没有对齐到512字节。(对于Advanced Format,最小对齐可能更大,甚至4096字节,或一个4K页面。)
未对齐偏移量的内核行为是灰色区域,此处提供了一些信息:RFC: Clarifying Direct I/O Semantics, Theodore Ts'o, tytso@mit, LWN, 2009
其他有趣的讨论还在这里:Linux: Accessing Files With O_DIRECT(内核陷阱,2007)
嗯,当DIRECT出现故障时,似乎应该回退到缓冲I/O。DIRECT的所有IO操作都是同步的。可能是一些DMA效应?或者是O_DIRECTmmap的组合?

  • 更新日期:*

下面是错误代码(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字节,这是未对齐的。

bbuxkriu

bbuxkriu3#

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

az31mfrm

az31mfrm4#

只是更新,因为这篇文章是第一个显示,如果你谷歌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年,还可以考虑将新的外部内存API与MemorySegment一起用于后备缓冲区。

相关问题