NIO与零拷贝
一、零拷贝
1.1 概述
零拷贝:
从OS的角度来看,文件的传输不存在CPU的拷贝,只存在DMA拷贝。在内核缓冲中,不存在重复数据(即只有kernel buffer中存在数据)。在Java程序中,常用的零拷贝有 mmap(内存映射)和 sendFile。零拷贝是网络编程和性能优化的关键。
零拷贝不仅带来更少的数据复制,还能减少线程的上下文切换,减少CPU缓存伪共享以及无CPU校验和计算。
1.2 拷贝方式
- 传统IO读写
4次拷贝,4次上下文(内核态和用户态)切换。
- mmap优化
- mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内存空间的数据。
- 即,用户缓冲区和内核读缓冲区的内存地址为同一内存地址,也就是说不需要CPU再把数据从内核读缓冲区复制到用户缓冲区。
- 在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。
- 需要进行4次上下文切换,3次拷贝。
- 适合小数据量的读写。
- sendFile优化
- Linux2.1 版本提供了 sendFile 函数,其基本原理如下:数据不经过用户态,直接从内核缓冲区进入到SocketBuffer,同时,由于和用户态完全无关,就减少了一次上下文切换。
- Linux 在 2.4 版本做了一些修改,避免了从内核缓冲区拷贝到 SocketBuffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。
- 在 2.4 版本中其实有一次CPU拷贝,kernel buffer -> socket buffer。但是拷贝的信息很少,只拷贝了数据的长度、偏移量等关键信息,消耗低,可以忽略不计。
- 需要3次上下文切换和最少2次拷贝。
- 适合大文件的传输。
Linux 2.1:
Linux 2.4:
NIO零拷贝:
- transferTo 底层使用到零拷贝:
- 通过系统调用sendfile()(当然这是Linux中的系统调用,Windows中系统调用有所不同)。
- 在Linux下,一个 transferTo 方法就可以完成传输。
- 在Windows 下一次调用 transferTo 只能发送 8M数据,就需要分段传输文件。