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

时间:2018-05-21 13:46:18

标签: 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");
        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.");
        }
    }
}

在Java 8上,运行正常(文件在tmpfs上,所以你可以期待它是微不足道的)

Average call time 1 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.
Average call time 0 us.

在Java 10上,随着文件变大,这种情况会越来越慢

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.

有没有办法诊断出这类问题?

是否有任何解决方案或替代方案可以在Java 10上有效运行?

注意:我们可以写到文件的末尾,但这需要锁定它,我们想避免这样做。

为了进行比较,在Windows 10上,Java 8(不是tmpfs)

Average call time 542 us.
Average call time 487 us.
Average call time 480 us.
Average call time 490 us.
Average call time 507 us.
Average call time 559 us.
Average call time 498 us.
Average call time 526 us.
Average call time 489 us.
Average call time 504 us.

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.

更新似乎系统调用的选择在Java 8和10之间发生了变化。这可以通过将strace -f添加到命令行的开头来看出

在Java 8中,在内循环中重复以下调用

[pid 49027] ftruncate(23, 53248)        = 0
[pid 49027] lseek(23, 0, SEEK_SET)      = 0
[pid 49027] lseek(23, 0, SEEK_CUR)      = 0

在Java 10中,重复以下调用

[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

特别是,fallocateftruncate做了更多的工作,所花费的时间似乎与文件的长度成正比,而不是添加到文件的长度。

一个解决方法是;

  • 将反射用于fd文件描述符
  • 使用JNA或FFI调用ftruncate。

这似乎是一个hacky解决方案。 Java 10中有更好的替代方案吗?

1 个答案:

答案 0 :(得分:19)

  

有没有办法诊断出这类问题?

您可以使用类似async-profiler的内核感知Java分析器。

以下是它为JDK 8展示的内容:

JDK 8 profile for RandomAccessFile.setLength

和JDK 10:

JDK 10 profile for RandomAccessFile.setLength

配置文件确认您的结论RandomAccessFile.setLength在JDK 8上使用ftruncate系统调用,但在JDK 10上使用fallocate更重。{/ p>

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

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

  

是否存在可在Java上高效运行的任何解决方案或替代方案   10?

内部类sun.nio.ch.FileDispatcherImpl具有静态truncate0方法。它使用了ftruncate系统调用。您可以通过Reflection调用它,但请记住这是一个私有的不受支持的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);