RandomAccessFile.setLength在Java 10(Centos)上慢得多

RandomAccessFile.setLength在Java 10(Centos)上慢得多,java,randomaccessfile,java-10,Java,Randomaccessfile,Java 10,下面的代码 public class Main { public static void main(String[] args) throws IOException { File tmp = File.createTempFile("deleteme", "dat"); tmp.deleteOnExit(); RandomAccessFile raf = new RandomAccessFile(tmp, "rw"); f

下面的代码

public class Main {
    public static void main(String[] args) throws IOException {
        File tmp = File.createTempFile("deleteme", "dat");
        tmp.deleteOnExit();
        RandomAccessFile raf = new RandomAccessFile(tmp, "rw");
        for (int t = 0; t < 10; t++) {
            long start = System.nanoTime();
            int count = 5000;
            for (int i = 1; i < count; i++)
                raf.setLength((i + t * count) * 4096);
            long time = System.nanoTime() - start;
            System.out.println("Average call time " + time / count / 1000 + " us.");
        }
    }
}
在Java10上,随着文件变大,速度会变慢

Average call time 311 us.
Average call time 856 us.
Average call time 1423 us.
Average call time 1975 us.
Average call time 2530 us.
Average call time 3045 us.
Average call time 3599 us.
Average call time 4034 us.
Average call time 4523 us.
Average call time 5129 us.
有没有办法诊断这种问题

有没有在Java10上有效工作的解决方案或替代方案

注意:我们可以写入文件的末尾,但是这需要锁定它,我们希望避免这样做

作为比较,在Windows 10上,Java 8(不是tmpfs)

Windows 10、Java 10.0.1

Average call time 586 us.
Average call time 508 us.
Average call time 615 us.
Average call time 599 us.
Average call time 580 us.
Average call time 577 us.
Average call time 557 us.
Average call time 572 us.
Average call time 578 us.
Average call time 554 us.
更新:系统调用的选择似乎在Java8和Java10之间发生了变化。这可以通过将
strace-f
前置到命令行的开头来看到

在Java8中,以下调用在内部循环中重复

[pid 49027] ftruncate(23, 53248)        = 0
[pid 49027] lseek(23, 0, SEEK_SET)      = 0
[pid 49027] lseek(23, 0, SEEK_CUR)      = 0
在Java10中,重复以下调用

[pid   444] fstat(8, {st_mode=S_IFREG|0664, st_size=126976, ...}) = 0
[pid   444] fallocate(8, 0, 0, 131072)  = 0
[pid   444] lseek(8, 0, SEEK_SET)       = 0
[pid   444] lseek(8, 0, SEEK_CUR)       = 0
特别是,
fallocate
ftruncate
做的工作多得多,所用的时间似乎与文件的长度成正比,而不是与添加到文件中的长度成正比

一个解决办法是:

  • 使用对
    fd
    文件描述符的反射
  • 使用JNA或FFI调用ftruncate
这似乎是一个很难解决的问题。Java10中有更好的替代方案吗

有没有办法诊断这种问题

您可以像这样使用支持内核的Java探查器

下面是它为JDK 8显示的内容:

对于JDK 10:

这些概要文件证实了您的结论,即
RandomAccessFile.setLength
在JDK 8上使用
ftruncate
syscall,但在JDK 10上使用了更重的
fallocate

ftruncate
非常快,因为它只更新文件元数据,而
fallocate
确实分配了磁盘空间(或者在
tmpfs
的情况下分配物理内存)

此更改是在扩展文件大小以映射时尝试修复:SIGBUS。但后来意识到这是一个坏主意,并在JDK 11中恢复了修复:

是否有任何解决方案或替代方案可以在Java上有效地工作 十点

有一个内部类
sun.nio.ch.FileDispatcherImpl
,它具有静态
truncate0
方法。它在引擎盖下使用
ftruncate
syscall。您可以通过反射调用它,记住这是一个私有的不受支持的API

Class<?> c = Class.forName("sun.nio.ch.FileDispatcherImpl");
Method m = c.getDeclaredMethod("truncate0", FileDescriptor.class, long.class);
m.setAccessible(true);
m.invoke(null, raf.getFD(), length);
Class c=Class.forName(“sun.nio.ch.FileDispatcherImpl”);
方法m=c.getDeclaredMethod(“truncate0”,FileDescriptor.class,long.class);
m、 setAccessible(true);
m、 调用(null,raf.getFD(),length);

如果我一次编写一个页面进行测试,则时间平均为2 us。如果I
fallocate
一次多出一页,成本与文件大小成正比,而不是与添加的大小成正比。i、 e.它似乎正在对每个页面执行操作,包括已经存在的页面。
[pid   444] fstat(8, {st_mode=S_IFREG|0664, st_size=126976, ...}) = 0
[pid   444] fallocate(8, 0, 0, 131072)  = 0
[pid   444] lseek(8, 0, SEEK_SET)       = 0
[pid   444] lseek(8, 0, SEEK_CUR)       = 0
Class<?> c = Class.forName("sun.nio.ch.FileDispatcherImpl");
Method m = c.getDeclaredMethod("truncate0", FileDescriptor.class, long.class);
m.setAccessible(true);
m.invoke(null, raf.getFD(), length);