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