零拷贝:从操作系统角度说是没有cpu拷贝。
零拷贝不仅带来更少的数据复制,还带来其他的性能优势,例如更少的上下文切换,更少的cpu缓存伪共享以及无cpu校验和计算。
java程序中,常用的零拷贝是:内存映射(mmap)和sendFile
NIO零拷贝使用方式:transferTo
java 本身并不具备 IO 读写能力,因此 read 方法调用后,要从 java 程序的用户态切换至内核态,去调用操作系统(Kernel)的读能力,将数据读入内核缓冲区。这期间用户线程阻塞,操作系统使用 DMA(Direct Memory Access)来实现文件读,其间也不会使用 cpu
从内核态切换回用户态,将数据从内核缓冲区读入用户缓冲区(即 byte[] buf),这期间 cpu 会参与拷贝,无法利用 DMA
调用 write 方法,这时将数据从用户缓冲区(byte[] buf)写入 **socket 缓冲区**,cpu 会参与拷贝
接下来要向网卡写数据,这项能力 java 又不具备,因此又得从用户态切换至内核态,调用操作系统的写能力,使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 cpu
用户态与内核态的切换发生了 3 次,这个操作比较重量级
数据拷贝了共 4 次
先看下传统的IO数据读写代码如下:
传统IO模型:
注:DMA(direct memory access)直接内存拷贝不使用CPU
一、mmap优化
mmap通过内存映射,将文件映射到内核缓冲区,同时用户空间可以共享内核空间的数据。在进行网络传输的时候,减少内核空间到用户空间的拷贝次数。
二、sendFile优化
linux2.1版本提供了sendFile函数,数据根本不经过用户态,直接从内核态缓冲区进入到socket buffer,同时由于和用户态完全无关,就减少了一次上下文切换。
linux2.4做了进一步优化,避免从内核缓冲区拷贝到socket buffer的操作,直接拷贝到协议栈,从而再一次减少数据拷贝。
三、mmap和sendFile的对比
1、mmap适合小数据量的读写,sendFile适合大文件的传输
2、mmap需要4次上下文切换,3次数据拷贝;sendFile需要3次上下文切换,最少2次数据拷贝
3、sendFile可以使用DMA方式,减少cpu拷贝。mmap则不能