NIO与零拷贝
零拷贝基本介绍
- 零拷贝是网络编程的关键,很多性能优化都离不开,文件传输通过零拷贝提升性能
- 在Java程序中,常用的零拷贝有mmap(内存映射)和sendFile,那么,他们在OS里,到底是怎么样的一个的设计?我们分析mmapo和sendFIle这两个零拷贝
- 另外我们看一下NIO中如何使用零拷贝
传统IO数据读写
Java传统IO和网络编程的一段代码
术语介绍:
DMA:direcret memory access,直接内存拷贝,不适用CPU
所谓0拷贝是没有cpu拷贝,dma拷贝是不可避免的,不可能不从硬件读到kernel buffer里面去的,是从操作系统角度看得
传统IO拷贝过程:
read方法通过DMA拷贝将数据拷贝到内核buffer,再把这个内核buffer用cpu拷贝到用户buffer,我们的数据在用户buffer内进行修改,修改完毕之后,我们在用cpu拷贝到socketbuffer,最后用DMA拷贝拷贝到协议引擎
经历了4次拷贝,3次切换,一次读写进行了多次拷贝和切换
mmap优化:
- mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间数据,这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝数
- 过程DMA拷贝到内核buffer这步还是需要操作的,通过mmap userbuffer和kernel buffer可以共享数据,不用发生一次cpu拷贝,可以直接在内核buffer中进行修改,修改完成后,拷贝到socket buffer,然后通过DMA拷贝到协议栈
- 3次拷贝,3次切换,少了一次拷贝
sendFile优化:
Linux2.1版本提供网络sendFile函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到SocketBuffer,同时,由于和用户态完全无关,就减少了一次上下文切换
3次拷贝,2次切换
Linux在2.4版本中,做了一些修改,避免了从内核缓冲区拷贝到Socket buffer的操作,直接拷贝到协议栈,从而再减少一次数据拷贝,这次真正的实现了零拷贝,这里其实有一次数据的拷贝kernel buffer到socket buffer,但是拷贝的数据很少,例如length、offset,消耗很低,可以忽略
零拷贝再次理解
- 我们说的零拷贝,是从操作系统的角度来说的,因为内核缓冲区之间,没有数据时重复的,只有kernel buffer一份数据
- 零拷贝不仅仅带来更少的数据复制,还能带来其他的行性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算
mmap和sendFile的区别
- mmap适合小数据量读写,sendFile适合大文件传输
- mmap需要4次上下文切换,3次数据拷贝;sendFile需要3次上下文切换,最少两次数据拷贝
- sendFile可以利用DMA方式,减少CPU拷贝,mmap则不能,必须从内核拷贝到sokcet buffer
原生NIO存在的问题
- NIO的类库和API繁杂,使用麻烦:需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
- 需要具备其他的额外技能:要熟悉Java多线程编程,因为NIO编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序
- 开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等
- JDK NIO的Bug:例如臭名昭著的Epoll Bug,他会导致Selector空轮询,最终导致CPU100%,知道JDK1.7版本该问题仍旧存在,没有被根本解决