零拷贝基本介绍
- Java中,常用的零拷贝有mmap(内存映射),sendfile,dma,directl/O等。
- 在操作系统中,零拷贝指的是避免在用户态 (User-space) 与内核态(Kernel-space) 之间来回拷贝数据.
OS的I/O普通读写流程
- 用户read发起系统调用,由用户态进入内核态,通过DMA技术将磁盘中的数据copy到内核缓冲区中
- 当DMA完成工作后,会发起一个中断通知CPU数据拷贝完成,然后CPU再将内核态中的数据copy到用户态中
- 内核唤醒对应线程,同时将用户态的数据返回给该线程空间
- 用户态线程进行业务处理(堆内存、堆外内存倒腾数据)
- 当服务器对请求进行响应的时候,会发起系统调用,由内核将用户态的数据copy到内核态中
- 复制完毕后,再有网络适配器通过DMA技术将内核态缓冲区中的数据copy到网卡中,完成后,内核态会返回到用户态
- 最后由网卡将数据发送出去
总结:在这个过程中,如果不考虑用户态的内存拷贝和物理设备到驱动的数据拷贝,我们会发现,这其中会涉及4次数据拷贝。同时也会涉及到4次进程上下文的切换。所谓的零拷贝,作用就是通过各种方式,在特殊情况下,减少数据拷贝的次数/减少CPU参与数据拷贝的次数
DMA(Direct Memory Access)
- Direct Memory Access,DMA的作用就是直接将I/O设备的数据拷贝到内核缓冲区中。
- 使用DMA的好处就是I/O设备到内核之间的数据拷贝不需要CPU的参与,CPU只需要给DMA发送copy指令即可,提高了处理器的利用效率。
Mmap(全称memory map)
- mmap 通过内存映射,将文件映射到内核缓冲区,也就说将内核态和用户态的内存映射到一起,避免来回复制,也不必反复调用read/write等函数;同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。
- 一般来讲,mmap会代read方法:
- 如果这个时候系统进行I/O的话,采用mmap + write的方式,内存拷贝的次数会变为3次,上下文切换则依旧是4次。
sendFile优化?
Linux 2.1提供了sendFile函数,原理:数据根本不经过用户态,直接从内核缓冲区进入到Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换
注意:零拷贝从操作系统角度来看没有cpu拷贝
sendfile + DMA Scatter/Gather
- Linux2.4版本中做了一些修改,它可以读page cache中的数据描述信息 (内存地址和偏移量) 记录到socket cache中,由 DMA 根据这些将数据从读缓冲区拷贝到网卡/协议栈,比之前版本减少了一次CPU拷贝的过程
这里其实有一次cpu拷贝 kernel buffer -> socket buffer 但是,拷贝的信息很少,比如 length,offset,消耗低,可以忽略
Direct?I/O
- 之前的mmap可以让用户态和内核态共用一个内存空间来减少拷贝,其实还有一个方式,就是硬件数据不经过内核态的空间,直接到用户态的内存中,这种方式就是Direct?I/O。
- 换句话说,Direct?I/O不会经过内核态,而是用户态和设备的直接交互,用户态的写入就是直接写入到磁盘,不会再经过操作系统刷盘处理。
- 这样确实拷贝次数减少,读取速度会变快,但是因为操作系统不再负责缓存之类的管理,这就必须交由应用程序自己去做,譬如MySQL就是自己通过Direct I/O完成的,同时MySQL也有一套自己的缓存系统
- 同时,虽然Direct?I/O可以直接将文件写入磁盘中,但是文件相关的元信息还是要通过fsync缓存到内核空间中
对零拷贝的理解(补充:五个方面)
- 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据)。
- 零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算。
- 五个方面:
- 直接使用堆外内存,避免JVM 堆内存到堆外内存的数据拷贝
- CompositeByteBuf 类,可以组合多个 Buffer 对象合并成一个逻辑上的对象,避免通过传统内存拷贝的方式将几个 Buffer 合并成一个大的 Buffer。
- 通过 Unpooled.wrappedBuffer 可以将 byte 数组包装成 ByteBuf对象,包装过程中不会产生内存拷贝
- ByteBuf.slice 操作与 Unpooled.wrappedBuffer 相反,slice 操作可以将一个 ByteBuf 对象切分成多个ByteBuf 对象,切分过程中不会产生内存拷贝,底层共享一个 byte 数组的存储空间。
- 使用 FileRegion 实现文件传输,FileRegion 底层封装了 FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝,这属于操作系统级别的零拷贝。
mmap和sendFile的区别
- mmap适合小数据量读写,sendFile适合大文件传输
- mmap需要4次上下文切换,3次数据拷贝;sendFile需要3次上下文切换,最少2次数据拷贝
- sendFile可以利用DMA方式,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)
零拷贝应用案例--复制文件
NewIOServer
?AIO基本介绍
JDK7引入A Synchronous I/O,常用Reactor和Proactor模式。NIO就是用的Reactor,当有事件触发时,服务器端得到通知,进行相应的处理。目前应用不多。
?原生NIO存在的问题
- NIO的类库和API繁杂,使用麻烦:需要熟练握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
- 要熟悉 Java 多线程编程,因为NIO编程涉及到Reactor模式,必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序。
- 开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等。
- JDK NIO的Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。直到 JDK 1.7版本该问题仍旧存在,没有被根本解决。