这段时间在学习一些系统底层的知识,真后悔大学没有好好学习操作系统,导致好多文章看不懂。说到这不得不吐槽一下,像介绍系统层次的一些书籍好多都是中文翻译版,而大部分的中文翻译版大都语句晦涩,难懂,而且极易被误导。网上也有一些介绍文章,好多是连作者自己都没搞明白抑或是简单的复制粘贴,真是越看越迷糊。当然不乏有好的翻译书籍。不仅仅是我个人,好多大牛也都建议这样的书籍直接读英文原版好一些。有英语问题也没办法,程序员学习能力强,英语不行就干英语^_^。再回到主题,看到一篇不错的文章,然后推荐给朋友,朋友一看英文的就懒得看,所以这里LZ打算将这篇文章用自己三脚猫的英语简单的翻译一下,以巩固自己的所学,同时也分享给大家。以下是正文。

  这篇文章介绍了一个有大量io操作的运行在linux或者unix平台上的Java程序,如何用zero copy技术来提高IO性能。zero copy可以避免缓冲区间数据拷贝的次数,也可以减少用户态和内核态之间的的切换。

  大部分web服务器都要处理大量的静态内容,而其中大部分都是从磁盘文件中读取数据然后写到socket中。这种操作对cpu的消耗是比较小的,但也是十分低效的:内核首先从磁盘文件读取数据,然后从内核空间将数据传到用户空间,应用程序又将数据从用户空间返回到内核空间然后传输给socket(如果好奇数据为何如此来回传输,请继续看下文)。实际上,应用程序就相当于是个低效的中间者,从磁盘拿数据放到socket。

  每次数据在内核空间和用户空间传输就一次拷贝过程,这是需要占用一定的cpu周期和内存资源的。幸运的是你可以通过一个叫zero copy的技术来消除这些拷贝过程。使用了zero copy技术的应用程序的数据传输过程就是内核从磁盘文件读取数据直接传输到socket中,不再经过应用程序这个中间者。zero copy大大改善了应用程序的性能并且减少了用户态和内核态之间的切换次数。

  在linux或者unix系统上,Java类库通过java.nio.channels.FileChannel的transferTo()方法来应用zero copy。你可以通过这个方法把一个channel中读取到的字节传输到另一个channel,不再需要数据流经应用程序。在这篇文章中,我们首先展示了使用传统数据复制方式的一些情况,然后又通过transferTo来使用zero copy实现一个更高性能的方式。

传统的数据传输方式:

  像这种从文件读取数据然后将数据通过网络传输给其他的程序的方式(大部分应用服务器都是这种方式,包括web服务器处理静态内容时,ftp服务器,邮件服务器等等)其核心操作就是如下两个调用:

File.read(fileDesc,buf,len);
Socket.send(socket,buf,len);

  其上操作看上去只有两个简单的调用,但是其内部过程却要经历四次用户态和内核态的切换以及四次的数据复制操作。

  图一展示了数据从文件到socket的内部流程:

     

图一,传统的数据复制方式

  图二是用户态和内核态的切换过程:

   

图二,传统方式的上下文切换过程

  这些步骤涉及到如下过程:
    1、read()的调用引起了从用户态到内核态的切换(看图二),内部是通过sys_read()(或者类似的方法)发起对文件数据的读取。数据的第一次复制是通过DMA(直接内存访问)
       将磁盘上的数据复制到内核空间的缓冲区中。
    2、数据从内核空间的缓冲区复制到用户空间的缓冲区后,read()方法也就返回了。此时内核态又切换回用户态,现在数据也已经复制到了用户地址空间的缓存中。
    3、socket的send()方法的调用又会引起用户态到内核的切换,第三次数据复制又将数据从用户空间缓冲区复制到了内核空间的缓冲区,这次数据被放在了不同于之前的内核缓冲区中,这个缓冲区与数                         据将要被传输到的socket关联。
    4、send()系统调用返回后,就产生了第四次用户态和内核态的切换。随着DMA单独异步的将数据从内核态的缓冲区中传输到协议引擎发送到网络上,有了第四次数据复制。

  使用内核空间的缓冲区做中介(而不是直接将数据传输到用户空间)或许看上去是低效的,然而内核缓冲区做中介的引入就是为了改善进程的性能。从当应用程序读取文件数据这方面来说,如果读取的数据小于这个中介缓冲区的容量,那么中介缓冲区就可以提前缓存一大部分数据以供程序下次读取使用,从而提高性能。从应用程序写数据来说,这个中介缓冲区可以用来实现异步功能(当数据缓冲区数据满了后再写出去,较少了系统调用的次数)。
  不幸的是,这种方式也有它自己的瓶颈。当应用程序读取的数据比这个中介缓冲区的容量大很多的时候,数据就会在磁盘、内核空间、用户空间之间复制多次后才最终被传给应用程序。
  零拷贝技术就是通过消除这种多余的数据拷贝来改善性能的。

使用zero copy的数据传输方式:

  如果你再看一下传统的方式,你会发现实际上第二次和第三次数据拷贝是没有必要的。应用程序除了缓存一下数据然后传回到socket的缓冲区中啥也没干。我们可以通过直接从内核缓冲区把数据传输到socket关联的缓冲区来代替传统的方式。transferTo()方法可以帮你实现。下面是这个方法的定义:

public void transferTo(long position,long count,WritableByteChannel target);

  transferTo()方法将数据从一个channel传输到另一个可写的channel上,其内部实现依赖于操作系统对zero copy技术的支持。在unix操作系统和各种linux的发型版本中,这种功能最终是通过sendfile()系统调用实现。下边就是这个方法的定义:

#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

  可以通过调用transferTo()方法来替代上边的File.read()、Socket.send()

transferTo(position, count, writableChannel);

  图三 展示了通过transferTo实现数据传输的路径:

            图三,通过transferTo()实现数据拷贝

  图四  展示了内核态、用户态的切换情况:

        图四,tranferTo()下上下文的切换

  使用transferTo()方式所经历的步骤:
    1、transferTo调用会引起DMA将文件内容复制到读缓冲区(内核空间的缓冲区),然后数据从这个缓冲区复制到另一个与socket输出相关的内核缓冲区中。
    2、第三次数据复制就是DMA把socket关联的缓冲区中的数据复制到协议引擎上发送到网络上。

  这次改善,我们是通过将内核、用户态切换的次数从四次减少到两次,将数据的复制次数从四次减少到三次(只有一次用到cpu资源)。但这并没有达到我们零复制的目标。如果底层网络适配器支持收集操作的话,我们可以进一步减少内核对数据的复制次数。在内核为2.4或者以上版本的linux系统上,socket缓冲区描述符将被用来满足这个需求。这个方式不仅减少了内核用户态间的切换,而且也省去了那次需要cpu参与的复制过程。从用户角度来看依旧是调用transferTo()方法,但是其本质发生了变化:‘
    1、调用transferTo方法后数据被DMA从文件复制到了内核的一个缓冲区中。
    2、数据不再被复制到socket关联的缓冲区中了,仅仅是将一个描述符(包含了数据的位置和长度等信息)追加到socket关联的缓冲区中。DMA直接将内核中的缓冲区中的数据传输给协议引擎,消                       除了仅剩的一次需要cpu周期的数据复制。

  图五展示了收集操作下transferTo的工作流程

            图五

构建一个文件传输服务器

  接下来让我们用一个在客户端和服务器段传输文件的实例来实践一下我们的zero copy技术(代码到下面下载)。TraditionalClient.java和TraditionalServer.java 是基于传统的实现方式。TraditionalServer.java 是一个服务器程序,绑定在一个端口等待客户端的连接,然后从客户端的连接中一次读取4k字节的数据。TraditionalClient.java连接到服务器,然后从文件中读取数据通过网络传送给服务器。

  同样的,TransferToServer.java 和TransferToClient.java执行相同的功能,但是使用了transfeTo()方法将文件从服务器发送到客户端。

性能比较

  我们在内核为2.6版本的linux上运行了这个例子程序,并测试了在不同文件大小的情况下,传统的方式和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方式比传统方式少大约65%的时间消耗。对于那些需要大量读取io数据传输到另一个channel的服务器程序来说,使用zero copy方式性能上的提高是相当显著地。比如web服务器。

摘要

  我们展示了transferTo比传统方式上的性能优势,在从一个channel读取相同数据发送到另一个channel的操作上。如果有一个需要在channel间大量复制数据的应用程序,使用zero copy将会有一个更大的性能提高。

下载

描述 名字 大小
文章中的简单例子程序 j-zerocopy.zip 3kb

通过zero copy来实现高效的数据传输的更多相关文章

  1. 零拷贝-zero copy

    Efficient data transfer through zero copy Zero Copy I: User-Mode Perspective 0. 前言 在阅读RocketMQ的官方文档时 ...

  2. std::copy的使用

    看到有人在用std::copy这个东西,很简洁和爽啊,,所以找些帖子学习学习 http://blog.sina.com.cn/s/blog_8655aeca0100t6qe.html https:// ...

  3. Raknet—视频会议系统最佳的数据传输引擎

    RakNet是一个跨平台的C++和C#的游戏引擎,它主要是为高效的数据传输而设计,使用者可以通过它进行游戏和其他的程序的开发.RakNet虽然是一个游戏引擎,但同样也是一个非常好的视频会议系统传输引擎 ...

  4. kafka知识点详解

    第一部分:kafka概述 一.定义(消息引擎系统) 一句话概括kafka的核心功能就是:高性能的消息发送与高性能的消息消费. kafka刚推出的时候是以消息引擎的身份出现的,它具有强大的消息传输效率和 ...

  5. Kafka史上最详细原理总结

    https://blog.csdn.net/ychenfeng/article/details/74980531 Kafka Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(pa ...

  6. Kafka原理总结

    Kafka Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(partition).多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实 ...

  7. 用BCP从SQL Server 数据库中导出Excel文件

    BCP(Bulk Copy Program)是一种简单高效的数据传输方式在SQL Server中,其他数据传输方式还有SSIS和DTS. 这个程序的主要功能是从数据库中查询Job中指定step的执行信 ...

  8. Kafka原理详解

    Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(partition).多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量 ...

  9. 【转载】Kafka史上最详细原理总结

    Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(partition).多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量 ...

随机推荐

  1. ASP.NET MVC 学习笔记(一)

    很久很久没有在博客园写过东西了,很多大虾也说过展示自己最好的地方就是有一个博客作为笔记,展示一下自己的学习和研究成果. 最近决心将公司的一款产品改用MVC的方式实现,于是乎就开始在园子里面疯狂的寻找各 ...

  2. Async and Await 异步和等待

    [第一次这么耐下性子认真写博客,虽然觉得很认真了,当毕竟是第一次嘛,以后再看肯定觉得很不咋滴的,更何况园子里有那么多的高人和大侠,这篇文章就权当练练手了,熟悉一下用客户端发表博客了,也希望大家多多照顾 ...

  3. Backbone源码解析(五):Route和History(路由)模块

    今天是四月十二号,距离上次写博已经将近二十天了.一直忙于工作,回家被看书的时间占用了.连续两个礼拜被频繁的足球篮球以及各种体育运动弄的精疲力竭,所以很少抽时间来写技术博客.今天抽出时间把backbon ...

  4. STC12C5A60S2笔记4(复位)

    1. 基本特性 STC 单片机有5种复位方式: 1) 热启动复位: 1.1)外部RST引脚复位 第一功能复位脚,即管脚9 RST/P4.7,该管脚拉高维持24个时钟周期+10ms后,单片机进入复位状态 ...

  5. 使用OAuth打造webapi认证服务供自己的客户端使用(二)

    在上一篇”使用OAuth打造webapi认证服务供自己的客户端使用“的文章中我们实现了一个采用了OAuth流程3-密码模式(resource owner password credentials)的W ...

  6. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  7. 用canvas开发H5游戏小记

    自神经猫风波之后,微信中的各种小游戏如雨后春笋般目不暇接,这种低成本,高效传播的案例很是受开发者青睐.作为一名前端,随手写个这样的小游戏出来应该算是必备技能吧.恰逢中秋节,部门决定上线一个小游戏,在微 ...

  8. 使用CSS sprites减少HTTP请求

    sprites是鬼怪,小妖精,调皮鬼的意思,初听这个高端洋气的名字我被震慑住了,一步步掀开其面纱后发觉很简单的东西,作用却很大 神马是CSS 小妖精 CSS sprites是指把网页中很多小图片(很多 ...

  9. boost常用记录

    1.BOOST_FOREACH 经常会遍历容器,写for/while循环到手痛,使用BOOST_FOREACH可以减少我们的工作.支持容器vector/list/set/deque/stack/que ...

  10. Linux gcc的安装

    说明:我是在虚拟机里学习的Linux,很多问题也是和虚拟机有关的.今天就遇到了虚拟机安装gcc是一个问题. 首先查看gcc是否安装: 然后安装gcc.(我的错误就出现在这里.) 我用的虚拟机,练习平时 ...