一、传统文件传输过程
- 应用程序会将源文件数据读取到用户态的应用缓冲区中,这需要从磁盘进行 IO 读取,是较慢的操作
- 然后应用程序会将应用缓冲区的数据拷贝到内核缓冲区,这会触发内核态和用户态的上下文切换
- 内核缓冲区的数据会通过 Socket 进行 PACK 和 SEND 操作,进入到网络协议栈进行处理
- 数据到达目标主机后,会从网络缓冲区拷贝到目标主机内核的接收缓冲区
- 然后目标应用程序需要从内核接收缓冲区复制数据到自己的应用缓冲区
- 最后应用程序再从应用缓冲区写入到磁盘文件,又是一个较慢的磁盘IO操作
- 整个过程中存在多次数据复制和上下文切换,带来了较大的性能开销
二、零拷贝的实现方式
- 应用程序可以通过mmap系统调用,直接将源文件映射到进程的用户空间,避免应用层读取和复制
- 同时,可以通过sendfile系统调用,直接从mmap内存区域发送数据,不需要复制到内核区
- 目标主机也可以采用mmap,直接将网络数据映射到应用层缓冲区,省去内核复制
- 甚至还可以通过DMA直接内存访问技术,将文件缓冲区直接发送到网络,减少更多复制
- 这些技术可以缩减或跳过用户态和内核态之间的数据拷贝,有效减少上下文切换
三、Go语言中的实现
- Go 通过syscall.Mmap直接内存映射文件,避免应用层读写
- 设置File.Fd()可以跳过应用层缓冲,直接访问文件描述符
- 使用Writev写入scatter/gather数组,可以减少拷贝
- 调用splice在内核级别零拷贝切片数据
- Readv可以直接从缓冲区填充mmap区域,省去复制
- channel和buffer的组合可以高效传递指针和长度,缩减拷贝
四、零拷贝的应用场景
- 网络IO场景,TCP/IP协议栈可以直接访问Socket缓冲区,提升吞吐量
- 虚拟化和容器场景,通过DMA和设备直通提高I/O效率
- 数据库查询只读可以通过mmap共享内存,获取更高速度
- 多媒体解码场景,GPU可以无缝直接访问内存缓冲区
- 分布式文件系统可直接访问存储介质,不通过内核缓冲