Следующий код
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
В частности, fallocate
выполняет гораздо большую работу, чем ftruncate
и время, которое, по-видимому, пропорционально длине файла, а не длине, добавленной в файл.
Одна работа вокруг;
- использовать отражение в дескрипторе файла
fd
- используйте JNA или FFI для вызова ftruncate.
Это похоже на хакерское решение. Есть ли лучшая альтернатива в Java 10?