c# 从地址拷贝byte_面试必备的 “零拷贝” 问题!从头给你说!

465a3480dba50277d076b25df88b3541.png

本文作者:ksfzhaohui

来源:juejin.im/post/5cad6f1ef265da039f0ef5df

  • 前言
  • I/O概念
    • 1.缓冲区
    • 2.虚拟内存
    • 3.mmap+write方式
    • 4.sendfile方式
  • Java零拷贝
    • 1.MappedByteBuffer
    • 2.DirectByteBuffer
    • 3.Channel-to-Channel传输
  • Netty零拷贝
  • 其他零拷贝
  • 总结

前言

从字面意思理解就是数据不需要来回的拷贝,大大提升了系统的性能;这个词我们也经常在java nio,netty,kafka,RocketMQ 等框架中听到,经常作为其提升性能的一大亮点;

下面从I/O的几个概念开始,进而在分析零拷贝。

I/O概念

1.缓冲区

缓冲区是所有I/O的基础,I/O讲的无非就是把数据移进或移出缓冲区;进程执行I/O操作,就是向操作系统发出请求,让它要么把缓冲区的数据排干(写),要么填充缓冲区(读);

下面看一个java进程发起read请求加载数据大致的流程图:

de63f6b46e68c1472afe502a2ae33017.png

进程发起read请求之后,内核接收到read请求之后,会先检查内核空间中是否已经存在进程所需要的数据,如果已经存在,则直接把数据copy给进程的缓冲区;如果没有内核随即向磁盘控制器发出命令,要求从磁盘读取数据,磁盘控制器把数据直接写入内核read缓冲区,这一步通过DMA完成;

接下来就是内核将数据copy到进程的缓冲区;如果进程发起write请求,同样需要把用户缓冲区里面的数据copy到内核的socket缓冲区里面,然后再通过DMA把数据copy到网卡中,发送出去;

你可能觉得这样挺浪费空间的,每次都需要把内核空间的数据拷贝到用户空间中,所以零拷贝的出现就是为了解决这种问题的;

关于零拷贝提供了两种方式分别是:mmap+write方式,sendfile方式;

2.虚拟内存

所有现代操作系统都使用虚拟内存,使用虚拟的地址取代物理地址,这样做的好处是:

  1. 一个以上的虚拟地址可以指向同一个物理内存地址

  2. 虚拟内存空间可大于实际可用的物理地址;利用第一条特性可以把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样DMA就可以填充对内核和用户空间进程同时可见的缓冲区了,大致如下图所示:

cd746701afea12c4c9e37b32a6bec381.png

省去了内核与用户空间的往来拷贝,java也利用操作系统的此特性来提升性能,下面重点看看java对零拷贝都有哪些支持。

3.mmap+write方式

使用mmap+write方式代替原来的read+write方式,mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系;

这样就可以省掉原来内核read缓冲区copy数据到用户缓冲区,但是还是需要内核read缓冲区将数据copy到内核socket缓冲区

大致如下图所示:

924f4a78d12b1e914469620f3f3fd8c3.png

4.sendfile方式

sendfile系统调用在内核版本2.1中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。

sendfile系统调用的引入,不仅减少了数据复制,还减少了上下文切换的次数,大致如下图所示:

b22347b5e578a4f5cd4d7af1306d6088.png

数据传送只发生在内核空间,所以减少了一次上下文切换;但是还是存在一次copy,能不能把这一次copy也省略掉,Linux2.4内核中做了改进,将Kernel buffer中对应的数据描述信息(内存地址,偏移量)记录到相应的socket缓冲区当中,这样连内核空间中的一次cpu copy也省掉了;

Java零拷贝

1.MappedByteBuffer

java nio提供的FileChannel提供了map()方法,该方法可以在一个打开的文件和MappedByteBuffer之间建立一个虚拟内存映射,MappedByteBuffer继承于ByteBuffer,类似于一个基于内存的缓冲区,只不过该对象的数据元素存储在磁盘的一个文件中;

调用get()方法会从磁盘中获取数据,此数据反映该文件当前的内容,调用put()方法会更新磁盘上的文件,并且对文件做的修改对其他阅读者也是可见的;

下面看一个简单的读取实例,然后在对MappedByteBuffer进行分析:

public class MappedByteBufferTest {

public static void main(String[] args) throws Exception {
File file = new File("D://db.txt");
long len = file.length();
byte[] ds = new byte[(int) len];
MappedByteBuffer mappedByteBuffer = new FileInputStream(file).getChannel().map(FileChannel.MapMode.READ_ONLY, 0,
len);
for (int offset = 0; offset < len; offset++) {
byte b = mappedByteBuffer.get();
ds[offset] = b;
}
Scanner scan = new Scanner(new ByteArrayInputStream(ds)).useDelimiter(" ");
while (scan.hasNext()) {
System.out.print(scan.next() + " ");
}
}
}

主要通过FileChannel提供的map()来实现映射,map()方法如下:

    public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException;


分别提供了三个参数,MapMode,Position和size;分别表示:

MapMode:映射的模式,可选项包括:READ_ONLY,READ_WRITE,PRIVATE;

Position:从哪个位置开始映射,字节数的位置;Size:从position开始向后多少个字节;

重点看一下MapMode,请两个分别表示只读和可读可写,当然请求的映射模式受到Filechannel对象的访问权限限制,如果在一个没有读权限的文件上启用READ_ONLY,将抛出NonReadableChannelException;

PRIVATE模式表示写时拷贝的映射,意味着通过put()方法所做的任何修改都会导致产生一个私有的数据拷贝并且该拷贝中的数据只有MappedByteBuffer实例可以看到;

该过程不会对底层文件做任何修改,而且一旦缓冲区被施以垃圾收集动作(garbage collected),那些修改都会丢失;

大致浏览一下map()方法的源码:

    public MappedByteBuffer map(MapMode mode, long position, long size)throws IOException{
...省略...
int pagePosition = (int)(position % allocationGranularity);
long mapPosition = position - pagePosition;
long mapSize = size + pagePosition;
try {
// If no exception was thrown from map0, the address is valid
addr = map0(imode, mapPosition, mapSize);
} catch (OutOfMemoryError x) {
// An OutOfMemoryError may indicate that we've exhausted memory
// so force gc and re-attempt map
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException y) {
Thread.currentThread().interrupt();
}
try {
addr = map0(imode, mapPosition, mapSize);
} catch (OutOfMemoryError y) {
// After a second OOME, fail
throw new IOException("Map failed", y);
}
}

// On Windows, and potentially other platforms, we need an open
// file descriptor for some mapping operations.
FileDescriptor mfd;
try {
mfd = nd.duplicateForMapping(fd);
} catch (IOException ioe) {
unmap0(addr, mapSize);
throw ioe;
}

assert (IOStatus.checkAll(addr));
assert (addr % allocationGranularity == 0);
int isize = (int)size;
Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
if ((!writable) || (imode == MAP_RO)) {
return Util.newMappedByteBufferR(isize,
addr + pagePosition,
mfd,
um);
} else {
return Util.newMappedByteBuffer(isize,
addr + pagePosition,
mfd,
um);
}
}

大致意思就是通过native方法获取内存映射的地址,如果失败,手动gc再次映射;最后通过内存映射的地址实例化出MappedByteBuffer,MappedByteBuffer本身是一个抽象类,其实这里真正实例话出来的是DirectByteBuffer;

2.DirectByteBuffer

DirectByteBuffer继承于MappedByteBuffer,从名字就可以猜测出开辟了一段直接的内存,并不会占用jvm的内存空间;

上一节中通过Filechannel映射出的MappedByteBuffer其实际也是DirectByteBuffer,当然除了这种方式,也可以手动开辟一段空间:

ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(100);

如上开辟了100字节的直接内存空间;

3.Channel-to-Channel传输

经常需要从一个位置将文件传输到另外一个位置,FileChannel提供了transferTo()方法用来提高传输的效率

首先看一个简单的实例:

public class ChannelTransfer {
public static void main(String[] argv) throws Exception {
String files[]=new String[1];
files[0]="D://db.txt";
catFiles(Channels.newChannel(System.out), files);
}

private static void catFiles(WritableByteChannel target, String[] files)throws Exception {
for (int i = 0; i < files.length; i++) {
FileInputStream fis = new FileInputStream(files[i]);
FileChannel channel = fis.getChannel();
channel.transferTo(0, channel.size(), target);
channel.close();
fis.close();
}
}
}

通过FileChannel的transferTo()方法将文件数据传输到System.out通道,接口定义如下:

    public abstract long transferTo(long position, long count,
WritableByteChannel target)throws IOException;

几个参数也比较好理解,分别是开始传输的位置,传输的字节数,以及目标通道;transferTo()允许将一个通道交叉连接到另一个通道,而不需要一个中间缓冲区来传递数据;

注:这里不需要中间缓冲区有两层意思:第一层不需要用户空间缓冲区来拷贝内核缓冲区,另外一层两个通道都有自己的内核缓冲区,两个内核缓冲区也可以做到无需拷贝数据;

Netty零拷贝

netty提供了零拷贝的buffer,在传输数据时,最终处理的数据会需要对单个传输的报文,进行组合和拆分,Nio原生的ByteBuffer无法做到,netty通过提供的Composite(组合)和Slice(拆分)两种buffer来实现零拷贝

看下面一张图会比较清晰:

f0936138a237b97d917169d62693aa91.png

TCP层HTTP报文被分成了两个ChannelBuffer,这两个Buffer对我们上层的逻辑(HTTP处理)是没有意义的。但是两个ChannelBuffer被组合起来,就成为了一个有意义的HTTP报文

这个报文对应的ChannelBuffer,才是能称之为”Message”的东西,这里用到了一个词”Virtual Buffer”。

可以看一下netty提供的CompositeChannelBuffer源码:

public class CompositeChannelBuffer extends AbstractChannelBuffer {

private final ByteOrder order;
private ChannelBuffer[] components;
private int[] indices;
private int lastAccessedComponentId;
private final boolean gathering;

public byte getByte(int index) {
int componentId = componentId(index);
return components[componentId].getByte(index - indices[componentId]);
}
...省略...

components用来保存的就是所有接收到的buffer,indices记录每个buffer的起始位置,lastAccessedComponentId记录上一次访问的ComponentId;

CompositeChannelBuffer并不会开辟新的内存并直接复制所有ChannelBuffer内容,而是直接保存了所有ChannelBuffer的引用,并在子ChannelBuffer里进行读写,实现了零拷贝。

其他零拷贝

RocketMQ的消息采用顺序写到commitlog文件,然后利用consume queue文件作为索引;RocketMQ采用零拷贝mmap+write的方式来回应Consumer的请求;

同样kafka中存在大量的网络数据持久化到磁盘和磁盘文件通过网络发送的过程,kafka使用了sendfile零拷贝方式;

总结

零拷贝如果简单用java里面对象的概念来理解的话,其实就是使用的都是对象的引用,每个引用对象的地方对其改变就都能改变此对象,永远只存在一份对象。

End

最近热文阅读:1、18个Java8日期处理的实践,太有用了!2、Spring 5.1.13 和 Spring Boot 2.2.3 发布3、Lambda 表达式有何用处?如何使用?4、如果我是面试官,我会问你 Spring 这些问题?5、MySQL事务的实现原理6、不耍流氓,有答案的Zookeeper面试题7、Java并发:分布式应用限流 Redis + Lua 实践8、Redis为什么默认16个数据库?9、SpringBoot+RabbitMQ ,保证消息100%投递成功并被消费(附源码)10、一次非常有意思的 SQL 优化经历f6942c5b4a5aef7948669a0a06cee22e.png关注公众号,你想要的Java都在这里

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

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

相关文章

lda 可以处理中文_中文分词(jieba)和语料库制作(gensim)

本文的内容为以下两个部分&#xff1a;文本分词&#xff08;jieba&#xff09;语料库制作&#xff08;gensim&#xff09;结巴&#xff08;jieba&#xff09;分词在自然语言处理领域中&#xff0c;分词和提取关键词都是对文本处理时通常要进行的步骤。用Python语言对英文文本进…

js时间搓化为今天明天_秋冬国产搓背神器!360°无死角,让你搓背不求人,太舒服了...

秋冬国产搓背神器&#xff01;360无死角&#xff0c;让你搓背不求人&#xff0c;太舒服了&#xff01;夏季悄然离去&#xff0c;秋季快步走来&#xff0c;距离冬季也不远了~天气变冷以后&#xff0c;下班、放学回家了最惬意不过的就是洗个澡&#xff0c;如果泡完澡再搓个背&…

用html制作广告图片切换效果,基于jquery实现图片广告轮换效果代码

效果图:实现代码:hotmarquee*{margin: 0;padding: 0;}body{font: 12px;padding-top: 50px;padding-right: 200px;padding-bottom: 100px;padding-left: 200px;}ul{list-style: none;}img{padding: 2px;border: 1px solid #eee;}a{outline: none;}#imgs{width: 410px;margin-rig…

matplotlib柱状图上方显示数据_Python数据分析matplotlib可视化之绘图!

Matplotlib是一个基于python的2D画图库&#xff0c;能够用python脚本方便的画出折线图&#xff0c;直方图&#xff0c;功率谱图&#xff0c;散点图等常用图表&#xff0c;而且语法简单。Python中通过matplotlib模块的pyplot子库来完成绘图。Matplotlib可用于创建高质量的图表和…

应付账款账龄分析模板_企业财务报表分析论文应如何着手?

首先是企业的选择&#xff0c;最好选取上市公司进行分析&#xff0c;上市公司的财务数据比较透明&#xff0c;完全可以从新浪财经、中国证券网等平台获得详尽的报表数据资料&#xff0c;一般选取近三到五个年度。有了数据就可以着手分析了&#xff0c;我们可以从下面几个方法入…

dbeaver导入excel文件_PyQT5练习:制作Excel文件导入MySQL窗口

本文环境配置&#xff1a;系统>windows10&#xff1a;64位工具>PyCharm&#xff1a;2018.3.1语言>Python&#xff1a;3.7.1第三方库PyQT5&#xff1a;5.11.3pyqt5-tools&#xff1a;5.11.3.1.4PyMySQL&#xff1a;0.9.3openpyxl&#xff1a;2.5.12练手&#xff1a;制作…

编程中的蛇形填空问题_PCB—蛇形线的作用,这次真的是把你搞懂了

经常能看到论坛里有人在问蛇形线的问题。平时我们能看到蛇形线的地方大都是一些高速高密度板&#xff0c;好像带有蛇形线的板子就更高级&#xff0c;会画蛇形线就是高手了。网上关于蛇形线的文章也有很多&#xff0c;总感觉有些帖子的内容会误导新手&#xff0c;给人们带来困扰…

html5控制gif速度,gif加速软件 教你加快GIF图片的播放速度

一位狸友在编辑GIF动态图片时碰到了一个问题&#xff0c;就是不知怎么把GIF动画的播放速度加快(或减慢)。如果你也正巧碰到此类疑问或想知道GIF加速的方法&#xff0c;可以接着往下看哦。其实先理解了GIF动画的原理&#xff0c;并找对合适的gif编辑软件&#xff0c;解决这问题并…

android studio mvvm模板生成_使用Vue快速生成shape背景图

写在前面在日常的Android开发之中&#xff0c;我们通常都会根据UI图去手动创建shape或者selector背景图&#xff0c;虽说创建起来很简单&#xff0c;但是未免也会感到繁琐&#xff0c;因此也研究了一些这方面的知识&#xff0c;包括自定义shapedrawable、dataBinding&#xff0…

python 句子中没有中文_人生感悟经典句子,生活中可以没有诗歌,但不能没有诗意...

也曾有过一些热忱和勇气&#xff0c;为了自以为的命中注定颠沛流离&#xff0c;本想有一段花好月圆&#xff0c;却未曾想过就此天各一方。不是不爱&#xff0c;不是不会爱&#xff0c;只是少了坚持走下去的勇气。想必这是很多人的心声。错过抑或过错&#xff0c;都是爱情里常有…

图像迁移风格保存模型_图像风格迁移也有框架了:使用Python编写,与PyTorch完美兼容,外行也能用...

原标题&#xff1a;图像风格迁移也有框架了&#xff1a;使用Python编写&#xff0c;与PyTorch完美兼容&#xff0c;外行也能用选自Medium作者&#xff1a;Philip Meier 机器之心编译 编辑&#xff1a;陈萍 易于使用的神经风格迁移框架 pystiche。 将内容图片与艺术风格图片进行…

vivo设置全屏后状态栏黑色_vivo手机用2年内存占满,是这3个功能捣乱,一键按下全部释放...

vivo手机用2年内存占满&#xff0c;是这3个功能"捣乱"&#xff0c;一键按下全部释放经常使用手机如果没有清理习惯&#xff0c;那么应用一多&#xff0c;产生的文件就会变多&#xff0c;这样手机就会没用2年就出现卡顿的现象。今天来教大家怎样一键释放vivo手机内存。…

学计算机之路写一篇作文,我的学习之路作文(2篇)

我的学习之路作文(2篇)在我们平凡的日常里&#xff0c;大家对作文都再熟悉不过了吧&#xff0c;作文根据写作时限的不同可以分为限时作文和非限时作文。那要怎么写好作文呢&#xff1f;以下是小编整理的我的学习之路作文&#xff0c;仅供参考&#xff0c;欢迎大家阅读。我的学习…

js生日计算年龄_你知道用EXCEL可以从身份证中提取生日、性别、年龄、生肖吗?...

首先我们先了解下中国身份证号码的编排规则&#xff0c;中国公民身份证共18位&#xff0c;1~6位为省份地区信息码&#xff1b;7~14位为出生日期码&#xff1b;15~16位数字表示所在地的派出所的代码&#xff1b;第17位数字表示性别(奇数表示男性&#xff0c;偶数表示女性)&#…

docker 修改阿里镜像源_国内部署kubernetes集群的最佳实践(一)—官方教程+阿里镜像源

项目github地址&#xff1a;https://github.com/usualheart/install_k8s_official在国内环境下&#xff0c;借助阿里镜像源&#xff0c;按照官方的指导&#xff0c;使用脚本一步一步安装kubernetes。参考教程【官方】在ubuntu上安装dockerhttps://docs.docker.com/engine/insta…

html 指定对象为块元素,html内联(行内)元素、块级(块状)元素和行内块元素分类...

HTML可以将元素分类方式分为内联(行内)元素、块级(块状)元素和行内块元素三种。注&#xff1a;HTML是标签语言&#xff0c;那么既然是标签&#xff0c;就可以自己定义一些自己元素(如自定义的元素等)&#xff0c;自定义元素浏览器默认解析为内联元素&#xff0c;为防止不同浏览…

defaultdict python_在python中怎样使用defaultdict-百度经验

defaultdict是collection模块下面的一个类&#xff0c;用于在执行字典的时候&#xff0c;遇到没有设置的属性的时候&#xff0c;可以设置默认的值&#xff0c;那么在python中怎样使用defaultdict呢&#xff1f;下面小编就带大家来看看详细的教程&#xff01;工具/原料 python版…

微型计算机除具有计算机的一般特点外,10秋学期《计算机应用基础》第1次在线作业答案免费6/15...

10秋学期《计算机应用基础》第1次在线作业答案免费6/152011-02-17 00:02:57266有学员问关于10秋学期《计算机应用基础》第1次在线作业答案免费6/15的题目的参考答案和解析&#xff0c;具体如下&#xff1a;2010秋学期《计算机应用基础》第一次在线作业试卷总分&#xff1a;100 …

java面试换背景颜色_三年经验Java程序员记一次失败的面试分享:鹅厂三面让我体无完肤!...

欢迎关注专栏&#xff1a;里面定期分享Java架构技术知识点及解析&#xff0c;还会不断更新的BATJ面试专题&#xff0c;欢迎大家前来探讨交流&#xff0c;如有好的文章也欢迎投稿。程序员圈内那点事​zhuanlan.zhihu.com经过半年的沉淀&#xff0c;加上对MySQL&#xff0c;redis…

git 获取最新代码_github从远程仓库获取

前面我们说明了如何将本地的代码push到远程的仓库中&#xff0c;现在我们将学习从远程仓库上的获取代码到本地。1&#xff0c;建立一个新的文件夹&#xff0c;以从远端获取完整的git项目命令1&#xff1a;git clone https://github.com/huangguojie880/git-demo.git2&#xff0…