是什么让没有缓冲区的文件读取如此昂贵?
What makes reading from files without buffers so expensive?
最近,我创建了一个界面,它强制用户实现单个 fromStream(OutputStream)
,其默认方法如下所示:
public default T fromFile(File file) throws IOException {
try (InputStream stream = new FileInputStream(file)) {
return fromStream(stream);
}
}
很快就发现这是非常昂贵的(每 MB 几秒),因为直接从 FileInputStream
读取单个字节。
将它包裹在 BufferedInputStream
中解决了我的问题,但它给我留下了一个问题,为什么 FileInputStream
如此昂贵。
读取字节时文件通道不会关闭或打开,那么为什么首先需要缓冲区?
当您从文件中读取时,您必须一次读取一个块,因为这是硬件支持的唯一数量。如果你在没有缓冲的情况下一次读取一个字符,那么假设 512B 块,你将读取同一个块 512 次以读取整个块。如果您读取并缓冲,您将访问磁盘一次,然后从内存中读取。
访问磁盘比访问内存慢几个数量级,这不是个好主意。
如果您使用 read()
方法从无缓冲流中读取字节,JVM 最终将重复读取系统调用 OS 以从文件中读取单个字节。 (在幕后,JVM 可能正在调用 read(addr, offset, count)
,计数为 1。)
进行系统调用的成本很大。至少比常规方法调用多几个数量级。这是因为在以下方面有很大的开销:
- 在应用程序(非特权)安全域和系统(特权)安全域之间切换上下文。需要保存寄存器集,需要更改虚拟内存映射,需要刷新 TLB 条目等
- OS 必须做各种额外的事情来确保系统调用请求的是合法的。在这种情况下,OS 必须根据当前文件位置和大小确定请求的偏移量和计数是否正常,地址是否在应用程序的地址 space 内,并将其映射为可写。等等。
相比之下,如果您使用缓冲流,该流将尝试从 OS 大块读取文件。这通常会导致 many-thousand-fold 系统调用数量的减少。
事实上,这与文件在磁盘上的存储方式无关。的确,数据最终必须一次读取一个块,等等。但是,OS 足够聪明,可以进行自己的缓冲。它甚至可以 read-ahead 部分文件,以便它们在(内核)内存中为应用程序准备好,当它使系统调用读取它们时。
多个单字节 read()
调用极不可能导致额外的磁盘流量。唯一合理的情况是,如果您在每个 read()
之间等待很长时间...并且 OS 重用它缓存磁盘块的 space。
最近,我创建了一个界面,它强制用户实现单个 fromStream(OutputStream)
,其默认方法如下所示:
public default T fromFile(File file) throws IOException {
try (InputStream stream = new FileInputStream(file)) {
return fromStream(stream);
}
}
很快就发现这是非常昂贵的(每 MB 几秒),因为直接从 FileInputStream
读取单个字节。
将它包裹在 BufferedInputStream
中解决了我的问题,但它给我留下了一个问题,为什么 FileInputStream
如此昂贵。
读取字节时文件通道不会关闭或打开,那么为什么首先需要缓冲区?
当您从文件中读取时,您必须一次读取一个块,因为这是硬件支持的唯一数量。如果你在没有缓冲的情况下一次读取一个字符,那么假设 512B 块,你将读取同一个块 512 次以读取整个块。如果您读取并缓冲,您将访问磁盘一次,然后从内存中读取。
访问磁盘比访问内存慢几个数量级,这不是个好主意。
如果您使用 read()
方法从无缓冲流中读取字节,JVM 最终将重复读取系统调用 OS 以从文件中读取单个字节。 (在幕后,JVM 可能正在调用 read(addr, offset, count)
,计数为 1。)
进行系统调用的成本很大。至少比常规方法调用多几个数量级。这是因为在以下方面有很大的开销:
- 在应用程序(非特权)安全域和系统(特权)安全域之间切换上下文。需要保存寄存器集,需要更改虚拟内存映射,需要刷新 TLB 条目等
- OS 必须做各种额外的事情来确保系统调用请求的是合法的。在这种情况下,OS 必须根据当前文件位置和大小确定请求的偏移量和计数是否正常,地址是否在应用程序的地址 space 内,并将其映射为可写。等等。
相比之下,如果您使用缓冲流,该流将尝试从 OS 大块读取文件。这通常会导致 many-thousand-fold 系统调用数量的减少。
事实上,这与文件在磁盘上的存储方式无关。的确,数据最终必须一次读取一个块,等等。但是,OS 足够聪明,可以进行自己的缓冲。它甚至可以 read-ahead 部分文件,以便它们在(内核)内存中为应用程序准备好,当它使系统调用读取它们时。
多个单字节 read()
调用极不可能导致额外的磁盘流量。唯一合理的情况是,如果您在每个 read()
之间等待很长时间...并且 OS 重用它缓存磁盘块的 space。