代码之家  ›  专栏  ›  技术社区  ›  Peter Lawrey

Java 10上的随机访问文件集长度慢得多(CITOS)

  •  20
  • Peter Lawrey  · 技术社区  · 8 年前

    以下代码

    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中有更好的替代方案吗?

    1 回复  |  直到 8 年前
        1
  •  19
  •   ZhekaKozlov    8 年前

    有没有诊断这种问题的方法?

    可以使用内核感知的Java探查器 async-profiler .

    以下是JDK 8的示例:

    JDK 8 profile for RandomAccessFile.setLength

    对于JDK 10:

    JDK 10 profile for RandomAccessFile.setLength

    这些资料证实了你的结论 RandomAccessFile.setLength 使用 ftruncate JDK8上的系统调用,但要重很多 fallocate 在JDK 10上。

    F运行 非常快,因为它只更新文件元数据,而 休耕 确实分配磁盘空间(或物理内存,以防 tmpfs )

    此更改是为了修复 JDK-8168628 :在扩展文件大小以映射它时使用sigbus。但后来人们意识到这是一个坏主意,在JDK 11中修复了这个问题: JDK-8202261 .

    在Java上是否有有效的解决方案或替代方案? 10?

    有一个内部类 sun.nio.ch.FileDispatcherImpl 有静电的 truncate0 方法。它使用 F运行 引擎盖下的系统调用。您可以通过反射调用它,记住这是一个私有的不支持的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);