java中transferto_被朋友问到什么是零拷贝,我一脸懵逼…

前言

我们的Web应用多多少少都会处理一些静态内容,需要先从磁盘中读取到数据,在不经过修改后将此数据写入到套接字,伪代码如下:

read(file, tmp_buf, len);

write(socket, tmp_buf, len);

虽然看似简单,但是它的效率却不高,因为在这两个调用之后,数据已经被至少复制了四次,并且执行了大概相同数量的用户/内核态上下文切换,那么啥是用户/内核态呢?用户态是指当程序运行在3级特权级上时,因为这是最低特权级,是普通的用户进程运行的特权级,反过来,当程序运行在0级特权级上时,就可以称之为运行在内核态。而为了使应用程序访问到内核管理的资源,内核必须提供一组通用的访问接口,这些接口就叫系统调用,当我们需要做IO操作如open、read、write、就需要通过系统调用来和内核进行交互,但是系统调用的开销很大,要尽量减少系统调用的次数,因为系统调用会从用户态进入到内核态,用户态和内核态的频繁切换,会消耗大量的CPU资源,会影响数据传输的性能。用户态切换到内核态的还有俩种方式,异常和外围设备的中断。

那这里特权又是指什么?

从80286处理器开始,Intel引入了保护模式,特权级就是保护模式中的一个重要概念,操作系统的核心代码运行在最高特权级(0特权级)上,而用户程序运行在最低特权级(3特权级上),特权级1、2一般用于运行系统服务程序。

对于上面的例子,我们可以把他分为以下几个步骤:

read会进行系统调用,将导致上下文从用户模式切换到内核模式,然后由DMA从磁盘读取文件内容并将数据存储到内核地址空间缓冲区中。

将数据从内核缓冲区复制到用户缓冲区,然后read系统调用返回,导致上下文从内核切换回用户模式。

write系统调用导致上下文从用户模式切换到内核模式,执行第三次复制,将数据放入内核地址空间缓冲区,这次是将数据放入另一个缓冲区中,这个缓冲区专门与套接字关联。

write系统调用返回。

68bfdf3476ef0c6acd06a67b23f67ce1.gif

可以看出,首先内核读出磁盘中数据,然后将数据跨越内核推到应用程序,应用程序再次跨越内核将数据推回,写出到套接字。在这里应用程序实际上担当了一个中介角色,即将磁盘文件的数据转入套接字,所以在内核上下文和应用程序上下文之间复制数据是多余的,那么有什么办法可以将数据直接从内核上下文复制到内核上下文呢?

答案就是使用零拷贝,零拷贝技术可以使内核直接将数据从磁盘文件拷贝到套接字,而无需通过应用程序,这不仅大大地提高了应用程序的性能,而且还减少了内核与用户模式间的上下文切换。说简单一点就是避免CPU将数据从一块存储拷贝到另外一块存储,减少不必要的拷贝

Java 中零拷贝技术

transferTo

我们可以通过java.nio.channels.FileChannel中的transferTo()方法来在Linux系统上进行零拷贝,transferTo()方法直接将字节从它被调用的通道上传输到另外一个可写字节通道上,数据无需流经应用程序,在内部,它依赖底层操作系统对零拷贝的支持,Linux系统中,会被传递到sendfile()系统调用中,sendfile不仅减少了数据复制,还减少了上下文切换,可以将上下文切换次数从四次减少到两次,数据拷贝的次数从四次减少到三次。

步骤如下:

sendfile系统调用使DMA引擎将文件内容复制到内核缓冲区中,然后在被内核复制到与套接字关联的内核缓冲区中。

DMA将Socket缓冲区拷贝到网卡buffer中。

public class Test{

public static void main(String[] args){

long l = System.currentTimeMillis();

transferTo("/home/HouXinLin/apps/gradle/gradle-6.1.1-all.zip", "/home/HouXinLin/temp.zip");

System.out.println(System.currentTimeMillis() - l);

}

private static void stream(String src, String dest){

try {

BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File(src)));

BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(new File(dest)));

byte[] temp = new byte[2048];

int size = 0;

while ((size = bufferedInputStream.read(temp)) > 0) {

bufferedOutputStream.write(temp, 0, size);

}

bufferedInputStream.close();

bufferedOutputStream.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

private static void copy(String src, String dest){

try {

Files.copy(Paths.get(src), new FileOutputStream(new File(dest)));

} catch (IOException e) {

e.printStackTrace();

}

}

private static void transferTo(String src, String dest){

try {

FileChannel readChannel = FileChannel.open(Paths.get(src), StandardOpenOption.READ);

FileChannel writeChannel = FileChannel.open(Paths.get(dest), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

readChannel.transferTo(0, readChannel.size(), writeChannel);

readChannel.close();

writeChannel.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

经过测试,其他两种方式平均都在上百毫秒以上(文件大小138.5MB),而transferTo都在50多毫秒。

map

在说map方法前,先说说什么是mmap,mmap是Linux提供的一种内存映射文件方法,mmap系统调用使DMA引擎将文件内容复制到内核缓冲区中,然后与用户进程共享缓冲区,就不需要在内核和用户内存空间之间执行任何复制。mmap就是代替了read操作。

tmp_buf = mmap(file, len);

write(socket, tmp_buf, len);

这样的话我们可以减少了内核复制数据量的一半,当传输大量数据时,这种方式有很棒的效果,但是,这种方式是有缺陷的,存在隐患,在内存中映射文件时,当另一个进程将同一个文件截断时,那么write系统调用会因为访问非法地址被SIGBUS信号终止,SIGBUS默认会杀死进程,服务器可能因此被终止。

FileChannel.map方法可以把一个文件从position位置开始的size大小的区域映射为内存映像文件,返回MappedByteBuffer,MappedByteBuffer继承于ByteBuffer,map方法底层是通过mmap实现的,因此将文件内存从磁盘读取到内核缓冲区后,用户空间和内核空间共享该缓冲区。

他有三个参数,分别为:MapMode、Position、size

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

Position:从哪个位置开始映射,字节数的位置。

Size:从position开始向后多少个字节。

private static void map(String src, String dest){

try {

FileChannel readChannel = FileChannel.open(Paths.get(src), StandardOpenOption.READ);

MappedByteBuffer map = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size());

FileChannel writeChannel = FileChannel.open(Paths.get(dest), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

writeChannel.write(map);

readChannel.close();

writeChannel.close();

} catch (Exception e) {

e.printStackTrace();

}

}

这种速度也非常快,经过测试,和transferTo方法一样。

零拷贝给我们带来的好处

减少甚至完全避免不必要的CPU拷贝,从而让CPU解脱出来去执行其他的任务

减少内存带宽的占用

通常零拷贝技术还能够减少用户空间和操作系统内核空间之间的上下文切换

查看系统调用

在程序访问硬件设备,如读取磁盘文件,接收网络数据等等时,必须将用户态模式切换至内核态模式,通过系统调用访问硬件设备,而strace就可以跟踪到这个进程产生的系统调用,包括参数,返回值,执行消耗的时间。

我们将上面的程序打包成jar,然后执行如下命令:

strace java -jar Demo.jar

68bfdf3476ef0c6acd06a67b23f67ce1.gif

从输出中可以看到,内部会调用mmap这个函数。

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

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

相关文章

20个天才般的走心设计,真是太牛了!

全世界只有3.14 % 的人关注了爆炸吧知识科技发展一日千里,每天都有很多实用的小物被发明出来。下面就是20个超聪明的日常小发明,看过后你一定也想拥有!“页面”椅子,可以帮助用户调整座椅高度,灵感来自于书籍装上这个以…

Android拨号盘,支持T9搜索和号码搜索

之前做通讯录软件,其中在做拨号盘的时候一直为怎么实现T9输入烦恼,上网找了很多帖子,都没有满意的答案。不过最后终于是实现了,看社区内好像也有不少朋友需要,在此分享一下。这个是在我项目中提取出来的拨号盘案例&…

浅谈.Net异步编程的前世今生----异步函数篇(完结)

前言上一篇我们着重讲解了TPL任务并行库,可以看出TPL已经很符合现代API的特性:简洁易用。但它的不足之处在于,使用者难以理解程序的实际执行顺序。为了解决这些问题,在C# 5.0中,引入了新的语言特性,被称为异…

NSInteger,NSUInteger,NSNumber

2019独角兽企业重金招聘Python工程师标准>>> Objective-C入门教程10:数字类型(NSInteger,NSUInteger,NSNumber) 柳志超博客 Program Objective-C Objective-C入门教程10:数字类型(NSInteger,NSUInteger,NSNumber) p…

听说麦当劳,买一个雪糕就送一个男友!

1 麦当劳买雪糕免费送男友!▼2 当90后成了家长......▼3 不要跟有鼻子的人握手可能刚刚扣过鼻屎▼4 鸟:别瞎玩!快开车!▼5 凭实力当上群主!▼6 要是有喜欢的女生千万不要问她闺蜜的意见▼7 终于,我们…

windows server 2008更新补丁失败排错

首先描述故障故障:1,windows服务器上丢失了共享磁盘。2,打开服务器管理器报错3,更新补丁报错,错误代码:800B01004,手动安装.net 3.5安装包同样报错话说这次出差帮客户解决问题。遇到了这样一个错…

WPF实现统计图

WPF开发者QQ群: 340500857 | 微信群 -> 进入公众号主页 加入组织有小伙伴提出需要实现统计图。 由于在WPF中没有现成的统计图控件,所以我们自己实现一个。PS:有更好的方式欢迎推荐。01—代码如下一、创建 Basi…

手绘图解:从零维到十维空间

全世界只有3.14 % 的人关注了爆炸吧知识事情是这样的,这周我给学生讲3dmax的课。为了让学生了解三视图我就顺便科普了一下什么是零维、一维、二维、三维空间。讲完不过瘾,感觉一支粉笔一块黑板讲维度是一件很爽的事情,那么.........接下来请同…

ISA server的常见身份验证方式

ISA 2006的几种常用验证方式:1. 基本验证:此验证方式不会被加密,只是以明文的方式来传递信息,不安全。如果在“网络”的“内部”属性中将“域”选项卡里边的“选择域”来配置默认域,那么就会把用户送来的帐户与密码信息…

字节前端终于开源!吹爆!

Semi Design 发布,前端同学的福音大家好,我是鱼皮。最近,字节跳动的抖音前端技术团队开源了一款企业级应用设计系统 Semi Design 。这也是他们团队在 GitHub 上首次公开的项目,短短几天,就收获了 3.6 k 个 star。GitH…

CSS2-3常见的demo列子总结

CSS2-3常见的demo列子总结 阅读目录 1. css超过一行或者多行后显示省略号。2. css图片未知高度垂直居中完美解决方案。3. 学习使用 :before和 :after伪元素回到顶部1. css超过一行或者多行后显示省略号。 Css实现超过一行后显示省略号&#xff1b;代码如下&#xff1a;<p st…

18张难以置信的照片,封面这张你就没见过

全世界只有3.14 % 的人关注了爆炸吧知识感谢网络&#xff0c;只要点几下鼠标&#xff0c;就能看到我们以前从未见过的东西——有些甚至是难以置信的&#xff01;鲸鱼的心脏水中的鲨鱼卵幼年的箭鱼萌萌哒世界上最高的棕榈树&#xff0c;简直以为是PS的没见过的话&#xff0c;很容…

.NET 生态系统的蜕变之 .NET 6

.NET 6 是自.NET 4 框架以来生态系统看到的最大版本更新&#xff0c;虽然.NET Core 是2014年开始非常大的一项重大战略举措&#xff0c;但是.NET 6是真正的具有强大动力的非常重要的版本。2021年11月9日即将正式发布的.NET 6, 也许你认为.NET 5才刚刚发布&#xff0c;我才刚开始…

我看你还能坚持多久?!

1 我看你还能坚持多久&#xff01;▼2 依旧是熟悉的配方▼3 到哪儿都不愁工作......▼4 请问&#xff0c;当事喵作何感想&#xff1f;▼5 池塘危险&#xff0c;请勿靠近&#xff01;&#xff08;图源网络&#xff0c;侵删&#xff09;▼6 望周知&#xff01;▼7 实在是无…

Hello Blazor:(13)查找HTML元素对应.razor文件

前言Blazor是基于组件的开发&#xff0c;每个组件都是以一个.razor文件形式存在。当应用程序变得越来越大并且.razor文件的数量和层次结构越来越多时&#xff0c;想很快弄清页面上的HTML元素是由哪个组件生成的&#xff0c;就变得不那么容易了&#xff01;FindRazorSourceFile介…

C++STL之string (转)

在学习cSTL中的string&#xff0c;在这里做个笔记&#xff0c;以供自己以后翻阅和初学者参考。 1&#xff1a;string对象的定义和初始化以及读写 string s1; 默认构造函数&#xff0c;s1为空串 string s2(s1); 将s2初始化为s1的一个副本 string s3("valuee");…

当年的毒王熊猫烧香,现在怎么样了?

全世界只有3.14 % 的人关注了爆炸吧知识放假&#xff0c;小编来到了远在73公里之外的天后宫&#xff0c;终于是了了本命年的一桩心事。回想上一个本命年&#xff0c;当时小编还是沉迷扫雷和蜘蛛纸牌的孩子...但当时却发生了一件令我很不爽的事——“熊猫烧香”席卷全国&#xf…

$query php,phpQuery让php处理html代码像jQuery一样方便

简介如何在php中方便地解析html代码&#xff0c;估计是每个phper都会遇到的问题。用phpQuery就可以让php处理html代码像jQuery一样方便。DEMO我下的是onefile版&#xff1a;phpQuery-0.9.5.386-onefile.zip然后在项目中引用。html文件test.html&#xff1a;Spiderman City Driv…

那个成人总会遇到的小问题……

结语超模君就问问&#xff1a;我还有机会10万&#xff0b;吗&#xff1f;&#xff08;溜了溜了&#xff09;莱布尼茨德国数学家莱布尼茨&#xff0c;被后人誉为“百科全书式的天才”&#xff0c;他的研究涉及逻辑学、力学等40多个领域。他创建了数学理论&#xff1a;微积分学。…

java继承接口和泛型,JavaSE习题 继承接口和泛型

问答题&#xff1a;1.子类在什么情况下可以继承父类友好成员&#xff1f;答&#xff1a;在同一个包内2.子类通过怎样的方法可以隐藏继承的成员变量&#xff1f;答&#xff1a;声明一个与父类相同变量名的成员变量3.子类重写继承的方法原则是什么&#xff1f;答&#xff1a;保证…