以下代码
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,Java0.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
特别地,
fallocate
工作比
ftruncate
所用的时间似乎与文件的长度成正比,而不是添加到文件中的长度。
一个解决办法是;
-
使用反射到
fd
文件描述符
-
使用JNA或FFI调用ftruncate。
这似乎是一个老生常谈的解决方案。在Java 10中有更好的替代方案吗?