概述

很多web应用都会有大量的静态文件。我们通常是从硬盘读取这些静态文件,并将完全相同的文件数据写到response socket。这样的操作需要较少的CPU,但是效率有些低,它需要经过如下的过程:kernel从硬盘读取数据,越过kernel-user边界将数据传递给用户空间的web应用;用户空间的web应用再次越过kernel-user边界将完全相同的数据写回到kernel空间的socket。在将数据从硬盘传递到socket的过程中,用户空间web应用的角色相当于一个中介,并且有些低效。

数据每次经过kernel-user边界的时候,都需要被copy一次,这样会消耗CPU资源及内存带宽。幸运的是,我们可以使用一种被称为zero copy的技术来消除这些copy操作。使用zero copy技术的应用会请求kernel直接将数据从硬盘拷贝到socket,而无需再经过应用。zero copy极大地提升了应用的性能,并减少了内核态和用户态上下文切换的次数。

在Linux和UNIX系统上,Java类库通过java.nio.channels.FileChannel的transferTo()方法实现了对zero copy的支持。我们可以使用transferTo()方法将读取到的字节数组直接从被调用的channel传输到另一个可写的channel上,这个过程中数据流转不需要通过应用。

接下来我们会先讲解一下如何使用传统的多次copy的机制实现数据的传输,而后再演示下使用transferTo()方法实现的zero copy技术是如何提升性能的。

传统数据传输方案

思考一下如下的场景:从一个文件读取数据,通过网络将数据传递给另一个应用程序(这个场景描述了大部分服务器应用的行为,包括处理静态文件的WEB服务器,FTP服务器,Mail服务器等)。这个操作的核心步骤只有两步,我们看下代码:

1

2

File.read(fileDesc,buf,len);

Socket.send(socket,buf,len);

我们的代码只有两行,看起来很简单,但是服务器完成这个过程却需要在用户态和内核态之间进行4次上下文切换,也就是说在这个操作完成之前数据需要被copy 4次。下面的图片展示了服务器是如何将数据从文件传输到socket的。

图一:传统模式下数据拷贝过程:

图二:传统模式下内核态和用户态之间的上下文切换

涉及到的步骤包括:

调用read()方法导致了用户态到内核态的切换(参看图二)。在系统内部是通过sys_read()(或类似的其他方法)从文件读取数据。第一次copy(参看图一)是通过直接内存访问(DMA)引擎实现的,这次copy从硬盘上读取了文件内容并将之保存在内核空间的缓冲区中。

第二次copy发生在数据从内核缓冲区被copy到用户缓冲区时,此时read()方法也返回了。read()方法的返回导致了从内核态到用户态一次切换。现在数据是保存在用户空间的缓冲区中。

socket调用send()方法再次引起了用户态到内核态的切换。第三次copy再次将数据放回到内核缓冲区。不过这次的内核缓冲区和上次的不同,这次的缓冲区和目标socket相关。

调用的send()方法返回时,产生了内核态到用户态的上下文切换。这次DMA引擎将数据从内核缓冲区发送到protocol引擎,也就是第四次copy,这是一个独立异步的操作。

使用内核缓冲区作为中间层(而不是直接将数据传送到用户缓冲区)可能看起来有些低效。但是最初将内核缓冲区作为中间层引入进程的目的就是提升性能。在读取数据的时候,作为中间层的内核缓冲区的角色相当于“预读取缓存”,也就是说如果应用请求的数据量比内核缓冲区空间小,就会将一部分数据预读取到作为中间层的内核缓冲区中以供下一次请求使用。很显然,在请求的数据量比内核缓冲区空间小时,这样做可以显著地提升应用性能。在写数据的时候,多个中间层有助于更好地实现异步写(先将数据写到中间缓存,中间层快满时再批量写出)。

不幸的是,在请求的数据量大过内核缓冲区很多时,这种方法本身也会成为性能瓶颈:因为数据会在硬盘、内核缓冲区和用户缓冲区之间多次拷贝。

zero copy可以排除这些多余的copy来提升性能。

zero copy方案

重新思考一下传统的数据传输方案,将会发现第二次和第三次的copy行为实际上是不必要的。在传统方案里,应用做的事情只不过是缓存数据并将之转发到socket缓冲区,我们可以考虑直接将数据从读缓存发送到socket缓冲区中。transferTo()方法能让我们实现这种操作。

transferTo()方法的定义如下:

1

publicvoidtransferTo(longposition,longcount,WritableByteChanneltarget);

transferTo()方法可以将数据从FileChannel发送到指定的WritableByteChannel中。transferTo()方法需要依赖底层操作系统的支持才能实现zero copy。在UNIX系统和各种Linux系统中,支持zero copy的系统方法是sendfile(),这个方法可以将数据从一个文件描述符转发到另一个文件描述符中。

sendfile()方法定义:

1

2

#include

ssize_tsendfile(intout_fd,intin_fd,off_t*offset,size_tcount);

在概述中,我们写过两行代码演示传统数据传输的方法,演示代码中的file.read()和socket.send()两个方法的调用可以替换为调用transferTo()方法,示例如下:

1

transferTo(position,count,writableChannel);

下图演示了调用transferTo()方法时数据传输的路径:

下图演示了调用transferTo()方法时用户态和内核态上下文切换的过程:

调用transferTo()方法涉及到的步骤为:

调用transferTo()方法产生了第一次copy:DMA引擎将文件内容copy到了读缓存中。

然后系统内核将数据copy到与输出socket相关的内核缓冲区中。

第三次copy发生在DMA引擎将数据从内核socket缓冲区发送到protocol引擎时。

看看效果:

将用户态-内核态上下文切换由四次减少到了两次;

将数据的copy由四次减少到了三次(其中只有一次涉及到CPU)。

不过这样子还没有达到使用zero copy的目标。如果底层网卡支持收集操作的话,我们还可以去掉由内核完成的copy(即第二次copy)。在Linux Kernel2.4及以后的版本中,socket缓冲区描述符已经被调整到满足这种需求了。这样这个方案不仅仅是减少了上下文切换的次数,也消除了copy过程中对CPU依赖的部分。尽管用户还是在用transferTo()方法,但是其底层行为已经发生了变化:

调用transferTo()方法时,DMA引擎将文件内容copy到内核缓冲区中;

不再将数据copy到socket缓冲区中,只是将数据描述符(包含地址信息和长度信息)追加到socket缓冲区。DMA引擎直接将数据从内核缓冲区传递到protocol引擎,从而消除了仅剩的CPU copy。

下图展示了使用transferTo()方法和收集操作时copy的详情:

构建文件服务器

现在我们练习使用一下zero copy,就演示一下文件在客户端和服务器之间的传递(示例代码下载地址见文末)。TraditionalClient.java以及TraditionalServer.java是基于传统方案的实现,和新方法是File.read()和Socket.send()。TraditionalServer.java是一个Server端程序,它监听着一个特定的端口以让Client连接,每次会从socket读取4KB数据。TraditionalClient.java连接到Server上,从一个文件中读取(使用File.read()方法)4KB数据并通过socket将数据发送(使用Socket.send()方法)给Server。

类似的,TransferToServer.java和TransferToClient.java实现了相同的功能,不过使用的是transferTo()方法(调用了系统的sendfile()方法),将文件数据从Server端发送到了Client端。

性能比较

我们在一台Linux Kernel版本2.6的机器上执行了示例代码,以毫秒级的时间尺度比较了传统方案和transferTo()方案传输不同大小的数据文件的速度。下表为测试结果:

File size

Normal file transfer (ms)

transferTo (ms)

7MB

156

45

21MB

337

128

63MB

843

387

98MB

1320

617

200MB

2124

1150

350MB

3631

1762

700MB

13498

4422

1GB

18399

8537

可以看到,较之传统方案,transferTo() API降低了大约65%的时间消耗。对于需要在IO channel间进行大量数据copy和传输的应用(比如WebServer),transferTo()可以显著地提升性能。

总结

我们演示了使用transferTo()的性能优势,可以看到中间缓冲区copy(即使是发生在内核中)会有一定的性能损失。对于需要进行channel间大量数据copy的应用,zero copy技术可以显著地提升性能。

其他

###################

java zero copy 实现,关于Zero Copy相关推荐

  1. SAP WMSD集成之Copy WM Quantity – Not Copy WM qty as delivery qty into delivery But PGI

    SAP WM&SD集成之Copy WM Quantity – Not Copy WM qty as delivery qty into delivery But PGI 本文继续DEMO SA ...

  2. no copy constructor available or copy constructor is declared #39;explicit#39;

    今天新写了一个类.然后对这个类使用STL中的vector,碰到错误: no copy constructor available or copy constructor is declared 'ex ...

  3. python中的几种copy用法_Python3中copy模块常用功能及其他几种copy方式比较

    1.简单的共享引用: python中内置有小整数常量池和字符串常量池,在某个范围内的相同的数字或字符串分别赋给不同的变量,这些不同的变量都是指向同一块内存地址,这就是所谓的共享引用,举几个简单的例子: ...

  4. copy构造函数使用深copy

    =操作符的默认copy构造函数是浅copy,要是想使用深copy需要编写copy构造函数,编写深copy构造函数的形式如下,调用方式除了显示的调用,当首次定义对象,并使用=进行对象初始化的时候也会调用 ...

  5. copy语法 postgre_PostgreSql中COPY的用法

    一.Copy的基本语法 Copy的作用是复制数据在数据表和文件之间. Copy在Postgresql中的语法是(来自文档): 1. 将文件中的数据复制到表中: COPY table_name [ ( ...

  6. PostgreSQL:Java使用CopyManager实现客户端文件COPY导入

    在MySQL中,可以使用LOAD DATA INFILE和LOAD DATA LOCAL INFILE两种方式导入文本文件中的数据到数据库表中,速度非常快.其中LOAD DATA INFILE使用的文 ...

  7. Java将一个对象的属性值copy给另一个相同的对象

    import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor;pub ...

  8. java 将A对象的值copy到B对象(根据属性名设置属性值)

    package com.zyhao.openec.catalog.pojo; import java.lang.reflect.Field; import java.lang.reflect.Invo ...

  9. (部署新java程序,程序报错,需copy的一个包)——java使用siger 获取服务器硬件信息...

    mcat-siger.sh  查看是否安装siger rsync -aPuv /usr/lib64/libsigar-amd64-linux.so $i:/usr/lib64/ java使用siger ...

最新文章

  1. 友盟统计-页面访问路径
  2. 2015年第六届蓝桥杯 - 省赛 - C/C++大学B组 - F. 加法变乘法
  3. 出现ORA - 1017用户名/口令无效; 登录被拒绝 的问题
  4. html js点击字图片下拉,JavaScript实现文字与图片拖拽效果的方法
  5. 遇到的几个开机启动故障
  6. 和吴昊一起玩推理 Round 10 (第一季完结篇) —— 终极推理 —— L与夜神月的对话...
  7. 我的if else代码纯净无暇,一个字也不能简化
  8. vue学习项目之去哪儿网笔记
  9. 纷杂的Spring-boot-starter: 5 应用安全与spring-boot-starter-security
  10. 计算机主板vga损坏,主板常见故障维修思路 集成显卡(VGA) -电脑资料
  11. yolov5导出onnx用netron.app可视化搜索
  12. Win10+Linux双系统删除Linux
  13. linux常用命令大全,建议收藏
  14. pytest学习:setup、teardown、setup_class、teardown_class的区别
  15. 对比学习论文综述(part4 transformer + 总结)
  16. python代码实现房价预测
  17. Yapi断言——服务端接口测试的复杂与个性化断言
  18. SpringMVC 入门
  19. windows10怎么安装c语言,如何在Win10安装Microsoft Visual C ++ 2015运行时
  20. 利用word邮件合并批量导入照片

热门文章

  1. CentOS7 Firewall NAT 及端口映射
  2. cmake使用总结(转)---工程主目录CMakeList文件编写
  3. 使用GNS3和Cisco IOU搭建路由交换实验-安装篇
  4. SharePoint项目中新建类库的错误处理及项目建设中遇到的问题总结
  5. 应对不良网络文化的技术之一——网络信息抽取技术
  6. Session莫名丢失的原因及解决办法[转载]
  7. Dottext.Web.UI.Handlers.BlogExistingPageHandler
  8. SBO部分SQL查询奉献
  9. 友元 java_C++ 友元关系详解
  10. leetcode 877. 石子游戏(dp)