零拷贝技术_基因单拷贝

零拷贝技术_基因单拷贝零拷贝技术概述零拷贝技术指在计算机执行操作时,CPU不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及CPU的拷贝时间。它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现CPU的零参与,彻底消除CPU的负载。实现零拷贝用到的主要技术是DMA数据传输技术和内存区域映射技术零拷贝机制可以减少数据在内核缓冲区和用户进程缓冲区之间反复的I/O拷贝操作零拷贝机制可以减少用户进程地址空间之间因为上下文切换而带来的CPU开销物理内存和虚拟

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

零拷贝技术

概述

零拷贝技术指在计算机执行操作时,CPU不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及CPU的拷贝时间。它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现CPU的零参与,彻底消除CPU的负载。

实现零拷贝用到的主要技术是DMA数据传输技术和内存区域映射技术

  • 零拷贝机制可以减少数据在内核缓冲区和用户进程缓冲区之间反复的I/O拷贝操作
  • 零拷贝机制可以减少用户进程地址空间之间因为上下文切换而带来的CPU开销

物理内存和虚拟内存

由于操作系统的进程之间是共享CPU和内存资源的,因此需要一套完整的内存管理机制防止内存泄漏。

现代操作系统提供了一种对贮存的抽象概念,即是虚拟内存。虚拟内存为每个进程提供了一个一致的,私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉

物理内存

物理内存是相对于虚拟内存而言的。物理内存指通过物理内存条而获得的内存空间

虚拟内存

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存,而实际上,虚拟内存通常是被分割成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换,加载到物理内存中来

虚拟内存地址和用户进程紧密相关,一般来说不同进程里的同一个虚拟地址指向的物理地址是不一样的,每个进程所能使用的虚拟地址大小和CPU位数有关。实际上物理内存可能远远小于虚拟内存的大小,每个用户进程维护了一个单独的页表,虚拟内存和物理内存就是用过这个页表实现地址空间的映射的。

用户进程申请并访问物理内存的过程

  1. 用户进程向操作系统发出内存申请请求

  2. 系统会检查进程的虚拟地址空间是否被用完,如果有剩余,给进程分配虚拟地址

  3. 系统为这块虚拟地址创建内存映射,并将其放到该进程的页表当中

  4. 系统返回虚拟地址给用户进程,用户进程开始访问该虚拟地址

  5. CPU根据虚拟地址在此进程的页表中找到相应的内存映射,但是这个内存映射没有和物理内存关联,于是产生缺页中断

  6. os受到缺页中断后,分配真正的物理内存并将它关联到了页表相应的内存映射。中断处理完成后CPU就可以访问内存了

    缺页中断并不是每次都会发生的,只有os觉得有必要延迟分配内存的时候才用得着,很多时候会在系统分配真正物理内存并和内存映射进行关联

虚拟内存的优点

  • 地址空间:提供更大的地址空间,并且地址空间是连续的,使得程序编写,连接更加简单
  • 进程隔离:不同进程的虚拟地址之间没有关系,所以一个进程的操作不会对其他进程造成影响
  • 数据保护:每块虚拟内存都有相应的读写属性,这样就能保护程序的代码段不被修改,数据块不能被执行
  • 内存映射:有了虚拟内存之后,可以直接映射磁盘上的文件到虚拟地址空间
  • 共享内存
  • 物理内存管理

内核空间和用户空间

os的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的权限。为了避免用户进程直接操作内核,os讲虚拟内存划分为两部分,一部分是内核空间,一部分是用户空间。在Linux系统中,内核模块运行在内核空间,对应的进程处于内核态,而用户程序运行在用户空间,对应的进程处于用户态。

内核空间

内核空间总是驻留在内存中,它是为操作系统的内核保留的。应用程序是不允许直接在该区域进行读写或直接调用内核代码定义的函数的,按访问权限可以分为进程私有和进程共享:

  • 进程私有的虚拟内存:每个进程都有单独的内核栈,页表,task结构以及mem_map结构等
  • 进程共享的虚拟内存:属于所有进程共享的内存区域,包括物理存储器,内核数据和内核代码区域

用户空间

每个普通的用户进程都有一个单独的用户空间,处于用户态的进程不能访问内核空间中的数据,也不能直接调用内核函数的,因此要进行系统调用时,就要将进程切换到内核态。用户空间包括以下几个内存区域:

  • 运行时栈:每当一个函数被调用时,该函数的返回类型和一些调用的信息被存储到栈顶,调用结束后调用信息会被弹出并释放掉内存
  • 运行时堆:用于存放进程运行中被动态分配的内存段,位于BSS和栈中间的地址位。堆的效率要比栈低的多
  • 代码段:存放CPU可以执行的机器指令,该部分内存只能读不能写。通常代码区是共享的,其他执行程序可调用它
  • 未初始化的数据段:存放未初始化的全局变量
  • 已初始化的数据段:存放已初始化的全局变量,比如静态全局变量,静态局部变量和常量等
  • 内存映射区域:例如将动态库,共享内存等虚拟空间的内存映射到物理空间的内存

Linux I/O读写方式

Linux提供了轮询,I/O中断以及DMA传输这三种磁盘与主存之间的数据传输机制:

  1. 轮询:基于死循环堆I/O端口进行不断检测
  2. I/O中断方式:当数据到达时,磁盘主动向CPU发起中断请求,由CPU自身负责数据的传输过程
  3. DMA传输:在I/O终端的基础上引入DMA磁盘控制器,由DMA磁盘控制器负责数据的传输,降低了I/O终端操作对CPU资源的大量消耗

I/O中断原理

在DMA技术出现之前俺,应用程序域磁盘之间的I/O操作都是通过CPU中断完成的。每次用户进程读取磁盘数据时,都需要CPU中断,然后发起I/O请求等待数据读取和拷贝完成,每次的I/O中断都导致CPU的上下文切换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BtUqpfIh-1638711733230)(C:\Users\Leoyu\AppData\Roaming\Typora\typora-user-images\image-20211130215135794.png)]

  1. 用户进程向CPU发起read系统抵用读取数据,由用户态切换为内核态,然后一直阻塞等待数据的返回
  2. CPU在接收到指令以后对磁盘发起I/O请求,将磁盘数据先放入磁盘控制器缓冲区
  3. 数据准备完成以后,磁盘向CPU发起I/O中断
  4. CPU受到I/O中断以后将磁盘缓冲歌曲中的数据拷贝到内核缓冲区,再从内核缓冲区拷贝到用户缓冲区
  5. 用户进程切换回用户态,解除阻塞状态

DMA传输

DMA的全称叫做直接内存出去(Direct Memory Access),是一种允许外围设备直接访问系统主内存的机制。也就是说,基于DMA访问方式,系统主内存域硬盘或网卡之间的数据传输可以绕开CPU的全程调度。

整个数据传输操作在一个DMA控制器的控制下进行的。CPU除了在数据传输开始和结束时做一点处理外,在传输过程中CPU可以继续进行其他的工作。这样在大部分时间里,CPU计算和I/O操作都处于并行操作,使整个计算机系统的效率大大提高

  1. 用户进程向CPU发起read系统调用读取数据,由用户态切换为内核态,然后一直阻塞等待数据的返回
  2. CPU在接收到指令以后对DMA磁盘控制器发起调度指令
  3. DMA磁盘控制器对磁盘发起I/O请求,将磁盘数据先放入磁盘控制器缓冲区,CPU全程不参与此过程
  4. 数据读取完成后,DMA磁盘控制器会接收到磁盘的通知,将数据从磁盘控制器缓冲区拷贝到内核缓冲区
  5. DMA磁盘控制器向CPU发送数据都玩的信号,由CPU负责将数据从内核缓冲区拷贝到用户缓冲区
  6. 用户进程由内核态切换回用户态,解除阻塞状态

传统I/O操作

传统的访问方式是通过write和read两个系统调用时显得,通过read函数读取文件到缓冲区中,然后通过write函数把缓存中的数据输出到网络端口

read(file_fd, tmp_buf, len);
write(socket_fd, tmp_buf, len);

整个过程涉及2次CPU拷贝,2次DMA拷贝,以及4次上下文切换

  • 上下文切换:当用户程序向内核发起系统调用时,CPU将用户进程从用户态切换为内核态,当系统调用返回时,CPU将用户进程从内核态切换回用户态
  • CPU拷贝:由CPU直接处理数据的传送,数据拷贝时会一直占用CPU的资源
  • DMA拷贝:由CPU向DMA磁盘下达指令,让DMA控制器处理数据传送,传送完毕时把信息反馈给CPU,从而减轻CPU资源的占用率

零拷贝方式(前面铺垫一大堆,终于是到重点了)

零拷贝技术主要有三个实现思路:用户态直接I/O,减少数据拷贝次数以及写时复制技术

  • 用户态直接I/O:应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输。直接I/O不存在内核空间缓冲区和用户空间缓冲区之间的数据拷贝
  • 减少数据拷贝次数:在数据传输过程中,避免数据在用户空间缓冲区和系统内核空间缓冲区之间的CPU拷贝,以及数据在系统内核空间内的CPU拷贝,这也是当前的主流零拷贝技术的实现思路
  • 写时复制技术:写时复制指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么将其拷贝到自己的进程地址空间,如果只是数据读取则不需要进行拷贝

用户态直接I/O

这种方式绕过内核,极大提高了性能

用户态直接I/O只能适用于不需要内核缓冲区处理的应用程序,这些应用程序通常在进程地址空间有自己的数据缓存机制,由于CPU和磁盘I/O之间的执行时间差距,会造成大量资源的浪费,解决方案是配合异步I/O使用

mmap+write

使用mmap+write代替原来的read+write方式,减少了1次CPU拷贝操作。mmap是Linux提供的一种内存映射文件方法,即将一个进程的地址空间中的一段虚拟地址映射到磁盘文件地址

tmp_buf = mmap(file_fd, len);
write(socket_fd, tmp_buf, len);

使用mmap的目的是将内核中读缓冲区的地址与用户空间的缓冲区进行映射,从而实现内核缓冲区与应用陈鼓内存的共享,省去了将数据从内核读缓冲区拷贝到用户缓冲区的过程,内核读缓冲区仍需将数据到内核写缓冲区

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qlYjcY5R-1638711733234)(C:\Users\Leoyu\AppData\Roaming\Typora\typora-user-images\image-20211201000703550.png)]

基于mmap+write的零拷贝方式,整个拷贝过程会发生4次上下文切换,1次CPU拷贝和2次DMA拷贝

  1. 用户进程通过mmap函数向内核发起系统调用,上下文从用户态切换为内核态
  2. 将用户进程的内核空间的读缓冲区与用户空间的缓存区进行内存地址映射
  3. CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间的读缓冲区(read buffer)
  4. 上下文从内核态切换回用户态,mmap系统调用执行返回
  5. 用户进程通过write函数向内核发起系统调用,上下文从用户态切换为内核态
  6. CPU将读缓冲区中的数据拷贝到的网络缓冲区(socket buffer)
  7. CPU利用DMA控制器将数据从网络缓冲区拷贝到网卡进行数据传输
  8. 上下文奇幻会用户态,write系统调用执行返回

mmap主要是提高I/O性能,虽然减少了1次拷贝,但是如果mmap一个文件被另一个进程所截获,那么write系统调用会因为访问非法地址被SIGBUS信号终止,SIGBUS会杀死进程,服务器可能会因此被终止

sendfile

sendfile系统调用在Linux内核版本2.1中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。sendfile不仅减少了CPU拷贝的次数,还减少了上下文切换的次数

sendfile(socket_fd, file_fd, len);

通过sendfile系统调用,数据可以直接在内核空间内部进行I/O传输,从而省去了数据在用户空间和内核空间之间的来回拷贝,但是对用户空间是完全不可见

基于sendfile的零拷贝方式过程会发生2次上下文切换,1次CPU拷贝和2次DMA拷贝:

  1. 用户进程通过sendfile函数向内核发起系统调用,上下文从用户态切换为内核态
  2. CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间的读缓冲区
  3. CPU将读缓冲区的数据拷贝到网络缓冲区
  4. CPU利用DMA控制器将数据从网络缓冲区拷贝到网卡进行数据传输
  5. 上下文切换回用户态,sendfile系统调用返回

sendfile存在的问题是用户程序不能对数据进行修改,只是单纯地完成了一次数据传输过程

sendfile+DMA gather copy

Linux 2.4版本的内核对sendfile系统调用进行修改,为DMA拷贝引入了gather操作。它将内核空间的读缓冲区中对应的数据描述信息记录到相应的网络缓冲区中,由DMA根据内存地址,地址偏移量将数据批量地从读缓冲区拷贝到网卡设备中,这样就省去了内核空间中仅剩的1次CPU拷贝操作

sendfile(socket_fd, file_fd, len);

在硬件的支持下,sendfile 拷贝方式不再从内核缓冲区的数据拷贝到 socket 缓冲区,取而代之的仅仅是缓冲区文件描述符和数据长度的拷贝,这样 DMA 引擎直接利用 gather 操作将页缓存中数据打包发送到网络中即可,本质就是和虚拟内存映射的思路类似。

基于sendfile+DMA gather copy系统调用的零拷贝方式,整个过程会发生2次上下文切换,0次CPU拷贝以及2次DMA拷贝

  1. 用户进程通过sendfile函数向内核发起系统调用,上下文切换为内核态
  2. CPU利用DMA控制器将数据从主存或硬盘拷贝到内核空间的读缓冲区
  3. CPU将读缓冲区的文件描述符和数据长度拷贝到网络缓冲区
  4. 基于文件描述符和数据长度,CPU利用DMA控制器的gather/scatter操作直接批量地将数据从内核的读缓冲区拷贝到网卡进行数据传输
  5. 上下文切换回用户态,sendfile系统调用执行返回

sendfile+DMA gather copy只适用于将数据从文件拷贝到socket套接字上

splice

Linux 在 2.6.17 版本引入 splice 系统调用,不仅不需要硬件支持,还实现了两个文件描述符之间的数据零拷贝。

splice(fd_in, off_in, fd_out, off_out, len, flags);

splice系统调用可以在内核空间的读缓冲区和网络缓冲区之间建立管道,从而避免了两者之间的CPU拷贝操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-odtI1T7l-1638711733236)(C:\Users\Leoyu\AppData\Roaming\Typora\typora-user-images\image-20211201004623176.png)]

基于splice系统调用的零拷贝方式,整个拷贝过程会发生2

次上下文切换,0次CPU拷贝以及2次DMA拷贝:

  1. 用户进程通过splice函数向内核发起系统调用,上下文切换为内核态
  2. CPU利用DMA控制器将数据从主存或硬盘拷贝至内核的读缓冲区
  3. CPU在内核的读缓冲区和网络缓冲区之间建立起管道
  4. CPU利用DMA控制器将数据从网络缓冲区拷贝至网卡进行数据传输
  5. 上下文切换回用户态,splice系统调用执行返回

splice 拷贝方式也同样存在用户程序不能对数据进行修改的问题。除此之外,它使用了 Linux 的管道缓冲机制,可以用于任意两个文件描述符中传输数据,但是它的两个文件描述符参数中有一个必须是管道设备。

写时复制

写时复制指的是当多个进程共享同一块数据时,如果其中一个进程需要对这份数据进行修改,那么就需要将其拷贝到自己的进程地址空间中。这样做并不影响其他进程对这块数据的操作,每个进程要修改的时候才会进行拷贝,所以叫写时复制

缓冲区共享

Solaris上实现的fbuf(fast Buffer)

fbuf的思想是每个进程都维护着一个缓冲区池,这个缓冲区能被同时映射到用户空间和内核态,内核和用户共享这个缓冲区池

Linux零拷贝对比

无论是传统 I/O 拷贝方式还是引入零拷贝的方式,2 次 DMA Copy 是都少不了的,因为两次 DMA 都是依赖硬件完成的。下面从 CPU 拷贝次数、DMA 拷贝次数以及系统调用几个方面总结一下上述几种 I/O 拷贝方式的差别。

拷贝方式 CPU拷贝 DMA拷贝 系统调用 上下文切换
传统方式(read + write) 2 2 read / write 4
内存映射(mmap + write) 1 2 mmap / write 4
sendfile 1 2 sendfile 2
sendfile + DMA gather copy 0 2 sendfile 2
splice 0 2 splice 2

Java NIO零拷贝实现

在Java NIO中的通道(Channel)就相当于操作系统的内核空间的缓冲区,而缓冲区对应的相当于操作系统的用户空间中的用户缓冲区

  • 通道是全双工通信的,既可能是读缓冲区,也可能是网络缓冲区
  • 缓冲区分为堆内存和堆外内存,这是通过malloc()分配出来的用户态内存

堆外内存(DirectBuffer)在使用后需要应用程序手动回收,而堆内存(HeapBuffer)的数据在GC时可能会被自动回收。因此,在使用HeapBuffer读写数据时,为了避免缓冲区数据因为GC而丢失,NIO会先把HeapBuffer内部的数据拷贝到一个临时的DirectBuffer中的本地内存(native memory),这个拷贝涉及copyMemory()的调用,实现原理和memcpy()类似。最后将临时生成的DirectBuffer内部的数据的内存地址传给I/O调用函数,这样就避免了再去访问Java对象处理I/O读写

MappedByteBuffer

MappedByteBuffer是NIO基于内存映射(mmap)这种零拷贝方式的提供的一种实现,它继承自ByteBuffer。FileChannel定义了一个map()方法,它可以把一个文件从position位置开始的size大小区域映射为内存映像文件。

public abstract MappedByteBuffer map(MapMode mode, long position, long size)
        throws IOException;
  • mode:限定内存映射区域对内存映像文件的访问模式,包括只可读,可读可写和写时拷贝三种模式
  • position:文件映射的起始地址,对应内存映射区域的首地址
  • size:文件映射的字节长度,从position往后的字节数,对应内存映射区域的大小

RocketMQ和Kafka对比

RocketMQ 选择了 mmap + write 这种零拷贝方式,适用于业务级消息这种小块文件的数据持久化和传输;而 Kafka 采用的是 sendfile 这种零拷贝方式,适用于系统日志消息这种高吞吐量的大块文件的数据持久化和传输。但是值得注意的一点是,Kafka 的索引文件使用的是 mmap + write 方式,数据文件使用的是 sendfile 方式。

消息队列 零拷贝方式 优点 缺点
RocketMQ mmap + write 适用于小块文件传输,频繁调用时,效率很高 不能很好的利用 DMA 方式,会比 sendfile 多消耗 CPU,内存安全性控制复杂,需要避免 JVM Crash 问题
Kafka sendfile 可以利用 DMA 方式,消耗 CPU 较少,大块文件传输效率高,无内存安全性问题 小块文件效率低于 mmap 方式,只能是 BIO 方式传输,不能使用 NIO 方式
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/191201.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • MyBatis返回复杂Map结果集「建议收藏」

    MyBatis返回复杂Map结果集「建议收藏」MyBatis返回复杂结果集key为指定属性,value为实体类结果集mybatis返回map,key为指定属性,value为实体类结果集

  • dos的批量copy命令

    dos的批量copy命令1、主要是有需求,采取百度的,发现挺好的,还是帮作者推广一下。来自https://www.cnblogs.com/xiykj/archive/2004/01/13/13299548.html“Dos命令复制所有目录下同类型文件”2、需求:想拷贝文件夹内部的同类型文件(比如*.jpg),但是这个jpg文件在不同的文件夹下面,因此copy起来还是不方便,因此需要寻求命令代码解决3、命令是:for/r%iin(.jpg)*docopy“%i”/yd:\img其中,加粗的是变成自己需

  • C语言实现List实现(附完整源码)[通俗易懂]

    C语言实现List实现(附完整源码)[通俗易懂]实现LinkedList实现statkList结构体实现以下8个接口完整的list.h头文件源码完整的list.c源文件源码实现List实现的完整源码(main函数测试)List结构体structL{void*val;Lnext;};实现以下8个接口externLList_init(void);externLList_push(Llist,void*val);externintList_length(Llist);externvoid

  • Python中if __name__ == ‘__main__‘:的作用和原理「建议收藏」

    Python中if __name__ == ‘__main__‘:的作用和原理「建议收藏」if__name__==’__main__’:的作用一个python文件通常有两种使用方法,第一是作为脚本直接执行,第二是import到其他的python脚本中被调用(模块重用)执行。因此if__name__==’main’:的作用就是控制这两种情况执行代码的过程,在if__name__==’main’:下的代码只有在第一种情况下(即文件作为脚本直接执行)才会…

  • AQS原理及用法_aqs是什么意思

    AQS原理及用法_aqs是什么意思AQS原理及用法1AQS简介AQS全称为AbstractQueuedSynchronizer,是Java中的一个抽象类。AQS是一个用于构建锁、同步器、协作工具类的工具类(框架)。有了AQS之后,更多的协作工具类都可以方便得被写出来。有了AQS,构建线程协作类就容易多了。控制并发流程的类,都需要线程等待和唤醒的功能,这是这些类的共同特点,因此可以抽象出一个基类,这就是AQS。AQS广泛用于控制并发流程的类,如下图:其中Sync是这些类中都有的内部类,其结构如下:

  • 【Android】Mac系统Android开发环境搭建

    【Android】Mac系统Android开发环境搭建第一步检查下自己的电脑上有没有安装JDK,通过在终端中输入”java-version”,可以得到检验。第二步如果没有安装JDK,请移步Oracle官网的下载中心进行下载(需要登录Oracle账号和同意协议才能下载)【https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html】(官网下载速度是真心慢,我后面从其他网站上下载了一个JDK,搜索“JDKforMac下载”,也可以到中文社区下载

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号