零拷贝
Posted JaxYoun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了零拷贝相关的知识,希望对你有一定的参考价值。
零拷贝主要关注两个核心点:线程上下文切换次数、数据在内存中被拷贝的次数;
因为线程上下文切换和在内存中拷贝数据,这两种操作都很耗CPU时间,所以要提升效率,就要尽量减少这两种操作。
一、原始阶段(上图左):
最符合直觉的方式。
整个过程公发生:2次内存数据拷贝,4次线程上下文切换。
二、MMAP + write阶段(上图右):
在用户态和内核态间开辟了一块共享内存,从磁盘传输到内核态的数据就放在这块共享缓冲区上,用户缓冲区也可以共享地访问,省去了内核缓冲区到用户缓冲区的拷贝过程。
最后用户线程再发起内核调用write,将共享缓冲区的数据拷贝到socket缓冲区,供网卡读取。
整个过程公发生:1次内存数据拷贝,4次线程上下文切换。
三、sendfile阶段(上图左):
用户线程发起sendfile系统调用,此过程将内存缓冲区到用户缓冲区的拷贝过程剔除;
直接将数据从内核缓冲区拷贝到socket缓冲区,然后线程再切回到用户态。
整个过程公发生:1次内存数据拷贝,2次线程上下文切换。
四、sendfile + DMA收集阶段(上图右):
与上一阶段类似,用户线程发起sendfile系统调用,此过程也将内存缓冲区到用户缓冲区的拷贝过程剔除;
但不直接将数据从内核缓冲区拷贝到socket缓冲区,而是将数据在内核缓冲区中的地址和数据长度拷贝到socket缓冲区;
网卡读到socket缓冲区中的地址,发现数据不在socket缓冲区中,就让DMA直接去内存缓冲区中读数据。
最后线程再切回到用户态。
整个过程公发生:0次内存数据拷贝,2次线程上下文切换。
RocketMq中零拷贝
参考技术A说起零拷贝之前,先来了解下服务器中文件数据通过网络传输到客户端的流程。作为应用服务器,其中会有很多从磁盘中读取数据,然后应用程序对加载到内存中的数据进行处理,然后通过网卡发送给客户端,传统数据处理通过以下两个函数实现:
在这个过程中,数据流转的大致过程如下:
可以见到,在这个过程中发生了2次cpu copy和2次DMA copy,以及发生了数次cpu状态切换。 这个操作对于应用服务器来说很频繁,因此带来的开销也是非常大。
因此所谓的零拷贝就是,让其中的2次cpu拷贝省略掉,因为这两次cpu拷贝的数据其实已经在内存中,没有必要再让cpu参与进来进行数据的拷贝,浪费cpu。在大量文件读写的时候,这个优化带来的收益还是比较可观的。
零拷贝的实现方式有两种:
mmap通过虚拟内存映射,让多个虚拟地址指向同一个物理内存地址,用户空间的虚拟地址和内核空间的虚拟地址指向同一个物理内存地址,这样用户空间和内核空间共享同一个内存数据。这样DMA引擎从磁盘上加载的数据不需要在内核空间和用户空间进行复制,减少了一次cpu拷贝。
sendfile通过系统调用,并且规定了in_fd文件描述符必须是可以mmap的,sendfile只能将文件数据发送到socket中,sendfile减少了一次cpu状态的切换
无论是mmap结合write方式还是sendfile方式都只是减少了一次cpu拷贝,而后DMA引擎还具有了收集功能,可以在内核缓存区发送到socket缓冲区的时候避免掉cpu复制,只是将缓冲区地址和数据长度发送给socket缓冲区,然后DMA引擎通过收集功能直接读取收集数据发送到网卡中。这里依赖DMA引擎的收集功能省略掉了最后一次cpu拷贝,到此才是真正的零拷贝。
所谓的零拷贝就是避免数据在内核空间缓存区和用户空间缓缓冲区之间的复制,避免掉2次cpu复制,释放cpu。
在RocketMq中采用的是mmap()结合write()方式来实现零拷贝。
在java中还可以通过FileChannel.transferTo()来实现数据从文件描述符传输到socket中,它的底层是通过sendfile系统调用来实现。
以上是关于零拷贝的主要内容,如果未能解决你的问题,请参考以下文章