Linux下基于mmap实现写时复制的内存拷贝速度探究
写时复制版memcpy在Linux中的实现与性能分析
首先直接给结论:你确实可以通过文件映射的方式实现批量内存的写时复制(COW)效果,但这个方案的性能表现不能一概而论,得结合场景和内核版本来看
具体实现方案
按照你提到的思路,步骤大概是这样的:
- 创建一个文件(建议用临时文件,比如通过
tmpfile()创建,或者用memfd_create()创建匿名文件,后面会说为什么) - 调用
mmap()将源内存区域映射到这个文件上,权限要匹配源内存的读写属性,一般用MAP_SHARED标志 - 再调用一次
mmap(),以MAP_PRIVATE标志将目标内存地址空间映射到同一个文件,这样后续对目标内存的写入操作会触发内核的写时复制机制——只有被修改的内存页会被实际复制,未修改的页会和源内存共享物理页
方案的开销争议
你担心的额外开销确实存在,远不止页表操作这么简单:
- I/O开销:如果用的是普通磁盘文件,内核会把源内存的页同步到磁盘页缓存,甚至可能刷到物理磁盘,这会带来明显的I/O延迟。就算用
tmpfs(基于内存的文件系统),虽然没有物理磁盘I/O,但还是有文件系统元数据的操作开销 - 页缓存管理开销:内核需要维护该文件的页缓存条目,处理页的引用计数、脏页标记等,这些都是额外的内核态操作
- 系统调用开销:两次
mmap()以及后续的munmap()都是系统调用,会带来用户态到内核态的上下文切换,对于小内存块来说,这个开销甚至会超过直接memcpy()的成本
速度特性与内核版本的关联
这个方案的性能表现和内存大小、修改频率以及内核版本密切相关:
- 内存规模与修改频率:如果是超大内存块(几百MB以上)且后续很少修改目标内存,这个方案的优势会非常明显——不需要一次性复制所有数据,只有修改的页才会被复制。但如果是小内存块,或者后续会大量修改目标内存,那这个方案的开销会远高于直接
memcpy() - 内核版本优化:
- Linux 3.17引入的
memfd_create()可以创建匿名内存文件,完全避免磁盘文件的I/O开销,此时的开销主要是页表操作和页缓存管理,比普通文件映射快很多 - 4.14及以后的内核对
tmpfs的映射操作做了大量优化,减少了元数据操作的开销 - 新内核对
MAP_PRIVATE映射的COW逻辑也有优化,比如减少页表操作的锁竞争,提升多线程场景下的性能
- Linux 3.17引入的
另外补充一点:如果你的场景是进程间的内存共享COW,其实可以直接用匿名映射的MAP_PRIVATE|MAP_ANONYMOUS,但如果是同一进程内的内存COW复制,上述文件映射方案是可行的替代方案,但要权衡好性能。
内容的提问来源于stack exchange,提问作者einpoklum
相关产品推荐
相关产品推荐

