零拷贝
Refs
传统IO
cpu先从磁盘读取数据到内核缓冲区,然后拷贝到用户缓冲区。表现在代码中就是读取的时候先new一个buffer数组,然后读取进去。
如图,其中上下文切换和CPU拷贝是零拷贝主要优化的点。
![image-20231025101256325](/i/l/?n=23&i=blog/2818558/202310/2818558-20231025102827919-576331866.png)
一共四次上下文切换,两次CPU拷贝,两次DMA拷贝。
零拷贝概念
零拷贝并不是没有拷贝数据,而是减少用户态/内核态的切换次数以及CPU拷贝的次数。零拷贝实现有多种方式,分别是
- mmap+write
- sendfile
- 带有DMA收集拷贝功能的sendfile
注意:他们功能不全相同,后两者主要用于用户不需要操作数据的情况,因为只有内核在操作数据。
mmap
mmap()
(系统调用)作用是将内核缓冲区和用户缓冲区的虚拟内存映射到同样的物理内存,这样,读取数据的时候就不用从内核缓冲区拷贝到用户缓冲区了。其他部分不变。如图:
减少一次拷贝。
![image-20231025101659623](/i/l/?n=23&i=blog/2818558/202310/2818558-20231025102828728-2107503582.png)
sendfile
sendfile()
主要用于不用操作数据的情况,内核直接将数据拷贝到socket缓冲区,这样只需要两次内核用户态切换。如图
减少两次上下文切换和两次CPU拷贝。
![image-20231025101854058](/i/l/?n=23&i=blog/2818558/202310/2818558-20231025102829296-814070285.png)
DMA+sendfile
sendfile()
的优化由DMA支持,DMA可以根据文件描述符和长度在内存和外设进行数据拷贝。那么CPU只需要将核缓冲区中的文件描述符信息(包括内核缓冲区的内存地址和偏移量)发送到socket缓冲区。然后DMA去拷贝到网卡中即可。
减少两次上下文切换和三次CPU拷贝。
![image-20231025102110408](/i/l/?n=23&i=blog/2818558/202310/2818558-20231025102829809-827638418.png)
JavaNIO与零拷贝
FileChannel的transferTo()/transferFrom()
,底层就是sendfile() 系统调用函数。
Java NIO有一个MappedByteBuffer
的类,可以用来实现内存映射。它的底层是调用了Linux内核的mmap的API。