gpt4 book ai didi

java - Java 10 (Centos) 上的 RandomAccessFile.setLength 慢得多

转载 作者:IT老高 更新时间:2023-10-28 20:53:40 29 4
gpt4 key购买 nike

以下代码

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.

UPDATE 系统调用的选择似乎在 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 中是否有更好的替代方案?

最佳答案

Is there a way to diagnose this kind of problem?

您可以使用可识别内核的 Java 分析器,例如 async-profiler .

这是 JDK 8 显示的内容:

JDK 8 profile for RandomAccessFile.setLength

对于 JDK 10:

JDK 10 profile for RandomAccessFile.setLength

配置文件证实了您的结论,即 RandomAccessFile.setLength 在 JDK 8 上使用 ftruncate 系统调用,但在 JDK 10 上使用了更重的 fallocate

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

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

Is there any solution or alternative which works efficiently on Java 10?

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

关于java - Java 10 (Centos) 上的 RandomAccessFile.setLength 慢得多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50450317/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com