零拷贝:CPU 参与数据拷贝的操作
[知乎] 深入剖析Linux IO原理和几种零拷贝机制的实现
CPU 浪费在数据拷贝操作:
- 占用 CPU 时间
- 线程上下文切换 context switch
解决:
- CPU 不参与数据的拷贝工作:零
Producer 和 Consumer 之间数据的流动,需要多少次拷贝?
比如:Java 读取文件并通过网卡发送
- 磁盘 -> 磁盘内核缓冲区:FileInputStream.read()
- 磁盘内核缓冲区 -> JVM byte[](用户空间):CPU 参与
- JVM byte[] -> Socket 内核缓冲区:CPU 参与
- Socket 内核缓冲区 -> 网卡
Linux 的 IO
Linux 系统中,要从硬盘读取数据到内存中的方法:
- 轮询
- CPU 依次从磁盘读取数据
- CPU 中断
- CPU 下达读取命令,然后挂起 read 进程
- 当硬盘将部分数据写入内存后,使用硬件中断触发
- CPU 查询中断向量表,执行对应的程序,并唤醒 read 进程,读取下一份数据
- DMA 控制器,Direct Memory Access
- 独立的处理器,只能执行简单的数据操作
- CPU 直接命令 DMA 去执行全部数据的拷贝
使用 read() 和 write() 系统调用,会使得用户进程从用户态切换成内核态:
Linux 的系统调用:
mmap:
- 将用户空间的内存与内核空间的内存映射起来
sendfile:
- 直接把数据在内核空间拷贝,减少上下文切换
- 但用户无法修改数据
- 需要 CPU 参与拷贝工作
senfile + DMA Gather Copy:
- 需要硬件支持
- 只适用于将数据从文件拷贝到 socket 套接字上的传输过程。
splice:
- 在两个内存缓冲区直接建立管道,从而避免两者之间的 CPU 拷贝工作
Java NIO 中的零拷贝:
- MappedByteBuffer
- JDK1.7 引入了 ByteBuffer,因为 byte[] 受 JVM 管制,一定是存在于用户空间的
- ByteBuffer 可以映射到堆外内存
- FileChannel.transferTo
- sendfile + DMA Gather Copy
MQ 中使用的零拷贝:
TODO 多思考一下
RocketMQ:
- mmap + write
- 优点:适用于小文件传输,频繁调用时,效率高
- 缺点:DMA 利用少,会比 sendfile 多消耗 CPU,内存安全性控制复杂,需要避免 JVM Crash 问题
Kafka:索引文件和数据文件应该是使用不同的系统调用
- sendfile
- 优点:利用 DMA 减少 CPU 消耗,大文件传输效率高,无内存安全性问题
- 缺点:小文件效率低于 mmap,只能是 BIO 方式传输,不能使用 NIO 方式