【JAVA核心知识】46:什么是零拷贝Zero-copy

news2024/9/29 21:32:55

零拷贝相较于传统的IO流程拥有更高的数据发送效率,无论是RocketMq,Kafka还是Netty等都用到了零拷贝技术,那究竟什么是零拷贝呢,零拷贝又是通过什么方式提升数据发送效率呢?

首先我们要明白,一次数据发送过程就是将磁盘中的目标数据交给网卡传输出去的流程。磁盘以及网卡都属于硬件层。而应用程序是不能直接操作硬件的。如果要操作硬件,需要进行上下文切换从用户态切换到内核态由操作系统来完成硬件交互。关于用户态,内核态,上下文切换这些这里不再赘述。

传统IO

先看一下传统IO的数据发送流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tx2xcrtN-1673262092254)(D:\桌面\零拷贝.assets\image-20221128112429249.png)]

  1. 应用程序调用read函数,IO开始。进行上下文切换,从用户态切换到内核态。
  2. DMA控制器将磁盘中的数据拷贝到OS缓冲区
  3. CPU将OS缓冲区的数据拷贝到用户缓冲区。进行上下文切换,从内核态切换到用户态。
  4. 应用程序调用write函数往Socket中写数据。进行上下文切换,从用户态切换到内核态。CPU将用户缓冲区的数据拷贝到Socket缓冲区
  5. DMA控制器将Socket缓冲区中的数据拷贝到网卡中完成数据发送
  6. 进行上下文切换,从内核态切换到用户态。一次数据发送流程结束。

可以看出传统的IO流程包括4次上下文的切换,4次拷贝数据(两次CPU拷贝以及两次DMA拷贝)。

CPU拷贝和DMA拷贝

数据拷贝流程都是CPU来负责进行拷贝的。但是和磁盘,网卡这种硬件交互时,因为硬件的速度限制,如果CPU全程参与拷贝,那么就很浪费CPU的时间片。DMA便是用来优化这个过程的,DMA,英文全称是Direct Memory Access,即直接内存访问。DMA本质上是一块主板上独立的芯片,能在外设设备和内存存储器之间直接进行IO数据传输,CPU只需发起拷贝命令给DMA,DMA就能完成数据的拷贝。其拷贝过程不需要CPU的参与。 可以简单的理解为,DMA是硬件为CPU找的一个助手,对于硬件方面较慢的数据拷贝,CPU只需将指令发给DMA,DMA就能帮忙CPU完成数据拷贝,这样高效的CPU就能空下来处理其它事情。

这是一个硬件层次的优化,只需要知道有这个技术即可。

零拷贝技术

零拷贝并不是说不会发生任何数据拷贝,而是不发生OS缓冲区到用户缓冲区的拷贝。以此减少内核态和用户态之间切换以及CPU拷贝的次数。目前零拷贝技术有三种:

  1. mmap+write
  2. sendfile (以及升级版的带有DMA收集功能的sendFile)

mmap和sendFile各有优缺点,并不是说谁一定优于谁。各有各的适用场景。比如RocketMQ就是用的mmap,而RabbitMQ用的则是sendfile。

1 mmap+write

mmap通过内存映射来减少上下文切换与CPU拷贝。

内存映射

内存映射即在进程的虚拟地址空间创建一个映射,分为两种:

文件映射:文件支持的内存映射,把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件。

匿名映射:没有文件支持的内存映射,把物理内存映射到进程的虚拟地址空间,没有数据源。

即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程可以采用指针的方式读写操作这一段内存,而无需将数据由内核态拷贝到用户态。且系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。

可以理解为java中的浅拷贝。用户进程的缓冲指向OS的缓冲,这时用户进程对内存映射对象的操作

mmap就是一种内存映射文件的方法。

mmap

mmap函数原型如下:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr:指定映射的虚拟内存地址
length:映射的长度
prot:映射内存的保护模式
flags:指定映射的类型
fd:进行映射的文件句柄
offset:文件偏移量

Java中的实现是MappedByteBuffer,通过channel#map方法得到。

在这里插入图片描述

通过内存映射,IO流程就变成了这样

  1. 应用程序调用mmap函数,IO开始。进行上下文切换,从用户态切换到内核态。
  2. DMA控制器将磁盘中的数据拷贝到OS缓冲区
  3. mmap函数建立内存映射完毕。进行上下文切换,从内核态切换到用户态。
  4. 应用程序调用write函数通过内存映射往Socket中写数据。进行上下文切换,从用户态切换到内核态。
  5. CPU将OS缓冲区的数据拷贝到Socket缓冲区。
  6. DMA控制器将Socket缓冲区中的数据拷贝到网卡中完成数据发送
  7. 进行上下文切换,从内核态切换到用户态。一次数据发送流程结束。

关键就在于3,4,5三步。 mmap无需像传统IO一样操作数据需要通过CPU拷贝将OS缓冲区的数据拷贝到用户缓冲区。因此在第3步和第4步也就无需进行CPU的数据拷贝。发起write指令后,因为数据实际是在OS缓冲区的,所以CPU可以直接将数据从OS缓冲区拷贝到Socket缓冲区。

可以看到mmap+write的方式包括4次上下文的切换,3次拷贝数据(一次CPU拷贝以及两次DMA拷贝)。

sendFile

sendFile搜索Linux2.1版本内核引入的一个系统调用函数,原型如下:

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

out_fd:为待写入内容的文件描述符
in_fd:为待读出内容的文件描述符
offset:文件偏移量
count:指定在fdout和fdin之间传输的字节数

Java对sendfile的支持是NIO中的FileChannel.transferTo()或者transferFrom()

sendFile可以在两个文件描述符之间传输数据,整个传输过程在内核态完成,避免了数据在OS缓冲区和用户缓冲区之间的拷贝,而且减少了上下文切换次数;

在这里插入图片描述

sendFile流程是这样的:

  1. 应用程序调用sendFile函数指出源描述符和目标描述符,IO开始。进行上下文切换,从用户态切换到内核态。
  2. DMA控制器将磁盘中的数据拷贝到OS缓冲区
  3. CPU将OS缓冲区(源描述符)的数据拷贝到Socket缓冲区(目标描述符)。
  4. DMA控制器将Socket缓冲区中的数据拷贝到网卡中完成数据发送
  5. 进行上下文切换,从内核态切换到用户态。一次数据发送流程结束。

可以发现,sendfile实现的零拷贝仅仅发生了2次上下文切换以及3次拷贝(2次DMA拷贝+1次CPU拷贝)

sendfile +DMA scatter/gather实现的零拷贝

linux2.4 版本,sendfile进行了优化升级, 引入SG-DMA技术。其实就是对DMA拷贝加入了scatter/gather操作,可以让DMA在多个缓冲区实现一个简单的IO操作,比如从通道中读取数据到多个缓冲区,或者从多个缓冲区写入数据到通道。这使得数据可以直接从OS缓冲区到网卡。彻底避免了CPU拷贝。

在这里插入图片描述

流程如下:

  1. 应用程序调用sendFile函数指出源描述符和目标描述符,IO开始。进行上下文切换,从用户态切换到内核态。
  2. DMA控制器将磁盘中的数据拷贝到OS缓冲区
  3. CPU将OS缓冲区的文件描述信息(包括内存地址和偏移量)发送到socket缓冲区
  4. DMA通过文件描述信息直接将数据从OS缓冲区拷贝到网卡。
  5. 进行上下文切换,从内核态切换到用户态。一次数据发送流程结束。

可以发现,sendfile +DMA scatter/gather实现的零拷贝仅仅发生了2次上下文切换以及2次拷贝(2次DMA拷贝),实现了真正意思上不通过CPU搬运数据的零拷贝。

kafka和RocketMQ的零拷贝

从上面的流程来看sendFile是明显比mmap更高效的。但是因为sendFile相当于原汁原味的读写,直接将硬盘上的数据发送给网卡,如果需要对硬盘的数据做一定的修改再发送给网卡的话,就不适合使用sendFile了。
在基于这种区别,数据发送上,Kafka与RocketMQ采用了不同方式的零拷贝。Kafka采用了sendFile,而RocketMQ则采用了mmap.
RocketMQ为了写入的速率,是将所有的队列数据统一写入同一个CommitLog来实现顺序写。这就是导致消费时需要读出CommitLog进行应用层过滤,所以就不能用到sendFile+DMA的零拷贝,而只能使用mmap.
kafka则是同一个队列的数据存再一起,发送时无需过滤,因此可以使用sendFile来获得更高的发送效率。

PS:
【JAVA核心知识】系列导航 [持续更新中…]
关联导航:39:一文看懂RocketMQ
欢迎关注…

参考资料:
内存映射原理及mmap
【Linux内核】内存映射原理
什么是零拷贝
mmap 相比 sendFile 有什么优势?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/186157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java基础--方法

前言:介绍 Java 中方法的基本语法、分类、执行并分析参数传值。 关键字:方法、形参、实参、返回值、实例方法、静态方法、参数传值 程序引例–为什么要「方法」 public class IntroduceOfMethod {// 入口主方法。public static void main(String[] args…

docker 高级篇

一、DockerFile 1.1、概述 dockerfile 是用来构建docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。 为什么要有dockerfile呢 在基础篇我们讲过,比如我们下载个 ubuntu镜像里面不包含 vim、ifconfig等组件,这个时候 新增…

DES加密算法

DES算法原理 对称密码算法中的分组加密算法(对应于流密码) 密钥64位,56位参与运算8位校验位(校验位为:8、16、24、32、40、48、56、64) 加密原理 1. IP置换 将明文数据转化为二进制数,并将它…

Lnix文件权限的修改

首先我们要清楚Linux文件的权限信息 在Linux中输入ls -l 或者 ll查看文件和目录的详细信息 文件详情实例中,a目录的第一个属性用“d”标识这个a是一个目录。 anaconda-ks.cfg第一个属性用“-”标识他是一个文件。 在Linux文件详情的后面属性需要分为三组查看 rwx&am…

三种调用机制: 同步调用、异步调用、回调(同步/异步)

c并发编程-01-并发与并行_发如雪-ty的博客-CSDN博客 c并发编程02-什么是I/O_发如雪-ty的博客-CSDN博客 c并发编程03-I/O多路复用_发如雪-ty的博客-CSDN博客 c并发编程04-同步与异步_发如雪-ty的博客-CSDN博客_c同步和异步 c并发编程05-什么是回调函数_发如雪-ty的博客-CSDN…

Web前端:使用ReactJS构建的应用类型

使用ReactJS,你可以构建各种各样的应用程序,包括单页应用程序、渐进式web应用程序、移动应用程序、仪表板、电子商务平台、企业web应用程序以及社交媒体和消息应用程序。1.单页应用程序(spa)单页应用程序(SPA)基本上是一个网页,它通过使用从w…

MySQL(七):undo日志——保证事务的原子性

目录一、前言1.1 如何回滚事务1.2 事务id1.3 roll pointer 隐藏列1.4 trx_id 隐藏列二、undo日志2.1 undo日志的格式2.2 insert 对应的undo日志2.3 delete 操作对应的undo日志2.4 update操作对应的undo日志2.5 Undo页面链表2.6 undo日志写入过程2.6.1 Undo Log Header2.7 回滚段…

springboot请求参数绑定原理篇

上篇文章写了SpringBoot 参数接收只看这一篇文章就够了,只是写了使用方法,没有写为什么,原理是什么,这篇文章也是之前的预先的计划,稍微花点时间整理下,知其然知其所以然,才算是能彻底掌握&…

如何用IDEA创建SpringBoot项目

一、创建一个 Spring Initializr 工程 next后选择2.7.8版本,勾选以下几个 Web里的spring bootTemplate Engines 里的 ThymeleafSQL里的MyBatis Framework 和 Mysql Driver 然后finish完成 二、配置resources文件 2.1、 application.properties: #??…

Docker容器命令无权限,WEB访问403

问题背景(描述) 部署dockerWeb后,重启访问403,详细如下 docker容器正常运行,且开机自启 #通过如下命令开机自启 docker update --restart always 容器id但是访问web服务出现403. 进入容器后,输入命令提示如下: 解决方案 关闭selinux SELinux(Security-Enhanced…

【网络安全】记一次红队渗透实战项目

前言 【一一帮助安全学习(网络安全面试题学习路线视频教程工具)一一】 一、信息收集 信息收集非常重要,有了信息才能知道下一步该如何进行,接下来将用nmap来演示信息收集 1、nmap扫描存活IP 由于本项目环境是nat模式需要项目…

【Java基础】——面向对象:封装

【Java基础】——面向对象:封装一、类和对象二、类的结构:属性、方法、构造器1、属性2、方法2.1、方法的定义2.2、方法的重载2.3、可变个数的形参2.4、方法参数的值传递机制3、构造器3.1、构造器的特征3.2、构造器的作用:3.3、构造器重载三、封装与隐藏1…

细菌,真菌,病毒——感染,免疫反应以及治疗用药差异

谷禾健康 与人类密切相关的微生物 我们的世界大到浩瀚宇宙,小到微观下的生物分子。我们总说漫天繁星,其实身边微生物数量可能更多。动物、植物、真菌、细菌、病毒等,共同构成了丰富多彩的生命世界。 细菌、真菌、病毒是其中的三个大类&#x…

spring integration使用:消息路由

系列文章目录 …TODO spring integration开篇:说明 …TODO spring integration使用:消息路由 spring integration使用:消息路由系列文章目录前言消息路由的概念二、路由的分类基于内容的路由器spring integration中的实现RecipientListRoute…

Python property()函数:定义属性

我们一直在用“类对象.属性”的方式访问类中定义的属性,其实这种做法是欠妥的,因为它破坏了类的封装原则。正常情况下,类包含的属性应该是隐藏的,只允许通过类提供的方法来间接实现对类属性的访问和操作。因此,在不破坏…

AOP的四种增强方式

1. 前置增强,在核心功能之前执行的额外功能 public class MyBeforeAdvice implements MethodBeforeAdvice{Overridepublic void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {System.out.print("this is my before advice!");Str…

渲染函数render

文章目录节点、树以及虚拟 DOM树节点虚拟 DOMvue中render函数的作用render函数去创建子组件内容createElement官方文档参考节点、树以及虚拟 DOM 在深入渲染函数之前&#xff0c;了解一些浏览器的工作原理是很重要的。以下面这段 HTML 为例&#xff1a; <div><h1>…

user-select:none真的能禁止文本的复制粘贴吗?

1. 前言 面向搜索引擎开发时&#xff0c;我们经常看到这样的情况&#xff1a;登录后复制。 由于设置了css属性 user-select:none&#xff0c;此时鼠标无法实现选中文本&#xff0c;也就无法复制文本&#xff0c;通常会采用这种方式来禁止复制文本。打开开发者工具-审查元素&am…

k-means聚类

一、概述 当前人工智能技术实现的一种主要手段是机器学习&#xff0c;而机器学习能够解决的问题主要有三种&#xff1a;分类、聚类、回归&#xff0c;有监督的是分类&#xff0c;无监督的是聚类。所谓聚类&#xff0c;就是以一定的方法将一堆样本依它们本身的数据特性划分成不同…

docker安装mongdb

MongoDB是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据库的。它支持的数据结构非常松散&#xff0c;是类似json的bson格式&#xff0c;因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非…