热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

翻译Zerocopy

翻译Zero-copy文章原文地址http:www.ibm.comdeveloperw…1.文章中粗斜字体表示翻译的不是太好,还有待修改2.翻译的有不准确的地方,请留言,我会马上改

翻译 Zero-copy文章
原文地址 http://www.ibm.com/developerw…
1.文章中粗斜字体表示翻译的不是太好,还有待修改
2.翻译的有不准确的地方,请留言,我会马上改正

Efficient data transfer through zero copy

很多Web应用都服务于大量的静态资源,这些静态资源大都是从磁盘中读取,然后将同样的数据写回给要返回的socket。这个过程似乎要求cpu进行相对的运转,但是这样有点低效:kernel从磁盘中读取的数据跨过kernel-user的边界写入应用,然后应用跨过kernel-user边界把数据写入到socket. 事实上,应用作为一个从磁盘获取的数据然后传输到socket中去的低效中介物。

数据每次穿过user-kernel边界,数据必须被复制一份,这样会消耗CPU周期与内存带宽。然而幸运的是,能通过一个叫“zero copy”的技术来消除这些复制。使用zero copy的应用要求kernel直接把数据从disk复制到socket,不需要通过应用。zero copy极大的提升了应用的性能 ,也减少了在kernel-mode 与 user-mode 之间上下文切换的次数。

java的类包在linux和unix操作系统上可以通过调用java.nio.channels.FileChannel 里的transferTo()支持zero copy的。你能通过transferTo() 直接把一个channel的数据传输到另一个可写的channel 里,而不要求通过应用来完成数据传输。 这篇文章首先演示了用传统复制方法传输简单文件所带来的过高的开销,然后再展示使用zero copy技术中的transferTo()方法而获得更好的表现。

Date transfer: The traditional approach

考虑到在网络上从文件中读取到数据然后传输到另一个程序的场景。(这个情况描述了很多服务应用的行为,包括web静态文件应用, FTP服务,邮件服务,等等)这些操作的核心是列表1中的两点。

Listing 1. Copying bytes from a file to a socket

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

尽管Listing1 中只是一个概念性地简单地说明,内部的,复制操作需要在user-mode 和 kernel-mode 之间做4次上下文切换,并且操作完成前数据要被复制四次。图标1展示了这些数据怎么从文件中转移到socket中的:

Figure 1. Traditional data copying approach

《翻译 Zero-copy》

图表2展示了上下文切换

Figure 2. Traditional context switches

《翻译 Zero-copy》

过程包括一下:

  1. read()函数的调用导致了一次user-mode 到 kernel-mode的上下文切换(见图2)。内部的asys_read()调用(或者同等)被用于把数据从文件中读取出来。第一次复制(见图1)是以DMA引擎方式呈现的,DMA是从磁盘中读取数据后把这些数据存储到kernel address space buffer(内核地址空间缓冲区)。

  2. 大量被请求地数据从read buffer 复制到 user buffer,并且调用read()后返回结果。结果来自于函数调用引起从kernel-mode到user-mode的上下文切换。现在数据被存到了 user address space buffer( 用户地址空闲缓冲区)。

  3. send() socket 调用引起了从user-mode 到 kernel-mode 的上下文切换。第三次复制表现为又一次把数据存到了kernel address space buffer(内核地址空闲缓冲区)。这次,虽然,数据被放到了不同的缓冲区,这个缓冲区与目标socket相关。

  4. send() 系统调用返回,创造了第四次上下文切换。独立地和异步地,第四次复制发生是通过DMA引擎把数据从kernel buffer地数据传送到protocol引擎

中间kernel buffer地使用(不如直接把数据传输到user buffer)是低效地。但是中间kernel buffer被引用到程序中是为了提高性能的。当应用需要的数据没有达到kernel buffer的承载量,读的这边的中间buffer 允许kernel buffer 扮演“预读缓存”的角色。当请求的数据比kernel buffer承载量小的时候,性能就有一个大幅度地提高。在写的一边的中间buffer允许异步完成操作。

不幸地是,如果请求地数据比kernel的承载量大很多的话这种方法就会有一个瓶颈。数据在传递给appliction之前,在磁盘 ,kerenel buffer 和 user buffer 之间复制了多次。

zero copy 通过消除冗余的数据复制来提高性能

Data transfer: The zero-copy approach

如果再次检查传统的方法,你会发现第二次与第三次数据复制可以去掉的。应用除了缓存数据外没有做其他事情,然后将数据传回给socket buffer。相反,数据能被直接从read buffer 传输到 socket buffer 。transferTo() 方法就可以实现这个过程。Listing2 展示了transferTo()方法的特点:

Listing 2. The transferTo() method

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

transferTo()方法将数据从文件通道传输到可写数据的通道。内部,这个过程依潜在的操作系统必须支持zero copy;在unix和linux的各个版本中,这种调用被指定到了sendfile()系统调用,Listing3展示了数据从文件描述符传输到另一个描述符:

Listing 3. The sendfile() system call

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

在Listing1 中的 file.read()与socket.send()的函数调用能被单独的transferTo()方法代替,Listing4中展示:

Listing 4. Using transferTo() to copy data from a disk file to a socket

transferTo(position, count, writableChannel);

图表3 展示了当transferTo()方法被调用时,数据的流向:

Figure 3. Data copy with transferTo()

《翻译 Zero-copy》

图表4 展示了当 transferTo()方法被调用时,上下文切换的情况:

Figure 4. Context switching with transferTo()

《翻译 Zero-copy》

调用 transferTo()时发生的步奏如下Listing4:

  1. transferTo()的调用使文件内容通过DMA的方式被复制到read buffer。然后将数据复制到与输出的套接字相关联的内核缓冲区中。

  2. 第三次复制发生是通过DMA引擎把数据从kernel socket buffers传输到协议引擎。

这是一个提高:我们把上下文切换从4次减少到了2次,把数据复制的次数从4次减少到3次(3次中只有一次涉及CPU),但是还没有达到我们zero copy的目标。如果底层网络接口卡支持收集操作的话,那么我们就可以进一步减少内核的数据复制。在linux 内核2.4和之后的版本中,socket buffer 描述符为要适应这个要求而被修改。这个方法不仅仅能减少上下文切,也可以消除涉需要Cpu参与重复数据的复制。user-side 层的使用仍然保持不变,但是内部调用已经改变:

  1. transferTo()方法调用使文件内容通过DMA引擎被复制到了kernel buffer

  2. 无数据被复制到socket buffer 。 相反,只有描述符的地址的信息和数据长度的信息被附加到了socket buffer。DMA引擎直接把数据从kernel buffer 传送到protocol engine,因此消除了剩余在Cpu里的副本。

图5 展示了调用transferTo() 数据复制的过程:

Figure 5. Data copies when transferTo() and gather operations are used

《翻译 Zero-copy》

Building a file server

现在我们将zero copy 投入练习,用同一个例子在客服端与服务器端之间传输一个文件。TraditionalClient.java和 TraditionalServer.java是建立在传统复制场景的基础上,使用的是File.read()和Socket.send()方法。TraditionalServer.java是监听一个连接客户端的特定端口的服务端程序,然后一次从socket中读取4k大小的数据。TraditionalClient.java 用来连接服务端,从文件中读取(用 File.read()方法)4k数据,然后发送到服务端的socket通道上(用socket.send()方法)。

相似的是,TransferToServer.java和TransferToClient.java 起了同样的作用,但是用transferTo()方法代替上面的方法(File.read()和Socket.send()方法)去把数据从服务端传输到客户端。

Performance comparison

我们在运行着 2.6 kernel 的linux系统上执行了样本程序,并且测量一下传统方法和transferTo() 方法之间在传输不同文件大小的过程中运行时间。表1 展示了结果:

Table 1. Performance comparison: Traditional approach vs. zero copy

《翻译 Zero-copy》

正如你看见的,相比传统方式传输方式,transferTo() API 降低大约65%的时间消耗,这对于处理大规模从I/O通道复制到另一个通道的应用有了一个潜在的大幅度的性能提升,例如web服务器。

Summary

我们演示了 用transferTo()方法 相比 从一个通道读取数据并写同一样的数据到另一个通道的方法 有一个极大优势。中间的buffer复制-即使他们隐藏在kernel中-能有一个可测量的成本。在通道之间处理大量的数据复制的应用中,zore-copy技术提高了很大的性能。


推荐阅读
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
author-avatar
我爱大树茶
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有