mmap访问内存方式
mmap访问内存方式
请看下面的Java代码,这段程序是取自1brc竞赛的第一名的代码。
public static void main(String[] args) throws IOException, InterruptedException {
... ...
try (var fileChannel = FileChannel.open(java.nio.file.Path.of(FILE), java.nio.file.StandardOpenOption.READ)) {
long fileSize = fileChannel.size();
final long fileStart = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize, java.lang.foreign.Arena.global()).address();
final long fileEnd = fileStart + fileSize;
final AtomicLong cursor = new AtomicLong(fileStart);
... ...
}
... ...
}
该代码显示出了基于mmap的IO操作的高效。下面研究一下为什么这种方式高效。
mmap(memory-mapped files)比一般IO操作(如read/write系统调用)在某些情况下更快的原因主要包括以下几点:
-
减少数据复制:
- 使用mmap时,文件被映射到进程的地址空间,使得可以直接读写内存来访问文件内容,省去了内核缓冲区与用户空间缓冲区之间的数据拷贝。当进行顺序读取或者大量随机访问时,这种直接访问方式避免了传统I/O中需要将内核缓存的数据复制到用户程序缓冲区的过程。
-
内存管理优化:
- 操作系统能够利用其虚拟内存和页替换机制,只在实际访问页面时才触发物理磁盘I/O(按需分页或预读)。这样可以更高效地利用有限的物理内存资源,并结合操作系统层面的缓存策略提升性能。
-
系统调用开销减小:
- 通过mmap读写文件时,一旦完成映射,后续对文件的操作就不必每次都涉及系统调用。而传统的read/write操作每次都会导致从用户态到内核态的切换,这一过程会带来一定的CPU开销。
-
缓存一致性:
- mmap映射的文件区域可以受益于操作系统的缓存机制,即如果其他进程也访问同一文件,那么这些更改可以通过缓存同步机制快速传播,无需额外的同步代码。
-
连续访问效率:
- 当应用程序对大文件进行连续访问时,由于内存访问的局部性原理(局部性原理指的是程序在执行过程中倾向于访问最近刚刚访问过的数据附近的数据),mmap可能触发硬件级别的预读取机制,从而提前加载接下来可能要访问的数据块,提高整体性能。
然而,在实际应用中,mmap并非总是优于传统的I/O操作。对于小文件、非顺序访问模式或者不完全访问整个文件的应用场景,mmap可能因为映射和维护内存映射表的开销而不占优势。此外,当内存资源紧张时,频繁的换页活动可能会增加磁盘交换,反而降低性能。因此,选择哪种方法取决于具体的应用需求和工作负载特征。