You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

大文件复制:mmap()+write()效率、系统优化及与copy_file_range对比

大文件复制:mmap()+write()的效率与系统优化疑问

我在开发中需要用C代码复制约10GB的大文件,由此产生以下疑问:

  • 将已有文件映射到内存后调用write()写入目标文件的mmap()+write()组合效率如何?
  • Windows、macOS、Linux、*BSD这类系统是否会对此场景进行特殊优化以提升文件复制性能?
  • 该方式的速度能否媲美copy_file_range(2)?Linux/*BSD的源码中是否有针对此场景的处理逻辑?

补充:我了解Linux下write(2)单次调用写入量不超过INT_MAX,因此项目中使用full_write()封装函数循环调用write(2)直至数据全部写入或调用失败。

以下是我项目中的示例代码(假设运行类Unix系统且所有系统调用均成功,input和output为常规文件):

int main() {
    struct stat sb;
    int fdsrc, fddst;
    void *p;

    fdsrc = open("input", O_RDONLY);
    fddst = open("output", O_WRONLY);

    fstat(fdsrc, &sb);
    p = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fdsrc, 0);
    write(fddst, p, sb.st_size);

    return 0;
}

回答

一、mmap()+write()的效率表现

mmap()+write()的效率在大文件复制场景中远不如专门的文件复制系统调用,核心原因如下:

  • 内存映射会将文件页加载到用户态地址空间,但调用write()时,内核必须先把这些用户态页的数据拷贝到内核态缓冲区,再写入磁盘——这多了一次用户态到内核态的冗余拷贝。
  • 对于10GB级别的大文件,一次性内存映射会触发大量页错误(page fault),导致内核频繁调度;若系统内存不足,还会引发页交换(swap),进一步拖慢复制速度。

二、各系统的优化情况

Linux

Linux内核不会对mmap()+write()的文件复制场景做特殊优化。这种方式本质是用户态发起的数据拷贝,内核无法识别这是“文件到文件”的复制操作,只能按常规write()流程处理。

*BSD

与Linux一致,*BSD内核也不会针对该场景做特殊优化。mmap()映射的页在write()时依然要经过用户态到内核态的拷贝,没有捷径可走。

macOS

macOS的XNU内核同样不会对该组合做特殊优化。不过macOS提供了fcopyfile()系统调用,专门用于高效文件复制,性能远高于mmap()+write()。

Windows

Windows下类似的内存映射是CreateFileMapping(),搭配WriteFile()写入时也无特殊优化。Windows的高效文件复制应使用CopyFile()或CopyFile2()这类原生API,它们会利用内核级直接拷贝逻辑。

三、与copy_file_range(2)的对比

copy_file_range(2)(Linux)、sendfile(2)(Linux/BSD)、fcopyfile()(macOS)这类专门的文件复制系统调用,效率远高于mmap()+write():

  • 这些调用是内核级直接文件拷贝,完全避免了用户态与内核态之间的数据拷贝——内核直接从源文件的页缓存读取数据,写入目标文件的页缓存,无需经过用户态内存。
  • 对于大文件,这类调用能充分利用内核的预读、写回优化,以及磁盘的连续IO特性,性能差距可达数倍。

Linux/*BSD源码中的处理逻辑

  • Linux:copy_file_range()通过copy_file_range_do_loop函数直接操作文件的页缓存,实现内核内的数据拷贝,完全绕开用户态。而write()处理mmap映射的内存时,会触发copy_from_user()操作,把数据从用户态拷贝到内核缓冲区,这一步是copy_file_range()没有的额外开销。
  • BSD:sendfile()或部分版本支持的copy_file_range()同样是内核内直接拷贝,write()处理mmap内存时依然需要用户态到内核态的拷贝,无特殊优化逻辑。

四、实践建议

  • 优先使用各平台的原生高效文件复制API:Linux用copy_file_range(2),macOS用fcopyfile(),Windows用CopyFile2(),BSD用sendfile(2)或copy_file_range()。
  • 如果必须使用mmap(),建议分块映射(比如每次映射256MB),避免一次性映射大文件导致的内存压力和页错误风暴,但即便如此,性能依然不如专门的复制API。
  • 确保write()的循环封装(比如你的full_write())能正确处理部分写入的情况,避免数据丢失。

内容的提问来源于stack exchange,提问作者complex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 02:20:04