通过zero-copy进行高效的数据传输(译)

这篇文章翻译自Efficient data transfer through zero copy。由于译者水平有限,有的地方翻译的可能不正确,所以读者应当先查看原文。

Web应用程序为了提供静态内容,需要先从磁盘中读出数据,然后将这些数据写入到Socket中。虽然这个过程不会占用太多CPU资源,但是它确实效率不高。内核从磁盘读取数据,需要在内核态和用户态之间切换,然后将它写入到Socket时,又需要从用户态切换到内核态。

当数据在用户态和内核态之间切换时,会消耗CPU资源以及内存带宽。我们可以通过一种叫做Zero-copy的技术来消除这些开销。zero-copy会将数据直接从磁盘拷贝到Socket,这就避免在内核态和用户态之间的切换。在Java中,java.nio.channels.FileChanneltransferTo()函数就是通过zero-copy来实现的。你可以通过transferTo()方法,直接将数据从一个Channel传输到另一个Channel。

在这篇文章中,首先通过一个使用传统方式实现的文件传输的例子,来查看其开销。然后通过一个用zero-copy实现的例子,来验证其性能优势。

使用传统的方式实现的文件传输例子

假设我们要开发一个用于从特定文件中读取数据,然后通过网络将它传输给其他的程序的例子。这个程序的核心,就是这么两个调用:

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

这很容易理解。但是,在其内部,却有着四次用户态和内核态之间的切换,并且,数据被拷贝了四次。下面这张图,展示了数据传输的过程:

而下面这张图,则展示了内核态和用户态之间切换的过程:

这四次用户态和内核态之间切换分别是:

  1. 当调用read()时,会导致一次从用户态到内核态的切换。read()的内部是通过sys_read()来实现的。第一次数据复制,是数据通过DMA被从文件读出并且放在了内核的buffer中。
  2. 数据从内核的读缓冲区被复制到应用程序的缓冲区,然后read()调用返回。这就会导致一次从内核态到用户态的切换。现在,数据被存储到了应用程序的缓冲区中。
  3. send()调用又会导致一次从用户态到内核态的切换。这里发生了第三次数据复制,数据从应用程序的缓冲区被复制到内核的写缓冲区。
  4. send()调用返回,这导致了第四次上下文切换。并且数据通过DMA从内核写缓冲区被发送到了协议栈。

即使中间的内核缓冲区(而不是直接将数据传输到应用程序的缓冲区中)似乎看起来效率很低。但是实际上,中间的内核缓冲区是为了提升性能而存在的。对于内核读缓冲区来说,它可以实现预先读,即,缓冲区中预先加载一些磁盘中的数据,下次应用程序在读数据的时候,就不需要再去磁盘读取,而是直接从内核的读缓冲区读取就好了,这是因为,有研究表明,应用程序总是倾向于读取连续的数据,即如果磁盘上的一块数据被访问到了,那么,在磁盘上,与这块数据相邻的其他数据,也很快就会被访问到。这就提高了读性能。而对于写缓冲区来说,它可以实现异步的写操作。即,对于写操作,将数据复制到内核写缓冲区,基本上就可以认为写入成功,而不需要一直阻塞到TCP协议栈真的发送了数据并收到了响应。

然而,内核缓冲区也有一些缺点。想象这样一种场景,应用程序请求10KB的数据,而内核缓冲区仅有4KB,那么,就需要分三次来读取磁盘数据,并复制到应用程序的缓冲区。对于内核的缓冲区也是这样。

通过zero-copy,就可以规避这些问题。

使用zero-copy实现的文件传输的例子

我们可以发现,在上面的例子中,实际上,第二次和第三次数据复制并不是必须的。应用程序仅仅是作为一个桥梁,将数据从内核读缓冲区传输到内核写缓冲区。实际上,数据可以直接从内核的读缓冲区写入到socket的缓冲区。Java中的transferTo()就实现了这个操作。

transferTo()方法的声明如下:

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

transferTo方法会将数据从FileChannel传输到WritableByteChannel。但是,这个方法依赖于操作系统的底层实现。只有当操作系统支持zero-copy时,这个方法才有用。在UNIX以及Linux的不同发行版中,这个方法最终会调用sendfile()系统调用。

sendfile()的声明如下:

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

下图展示了transferTo()方法的流程:

下图展示了使用transferTo()方法时,上下文切换的情况:

两次上下文切换分别发生在:

  1. 在调用transferTo()方法时,需要从用户态转换到内核态。然后数据通过DMA被从文件读到内核读缓冲区中。然后数据被从内核读缓冲区复制到Socket的缓冲区中。最后,数据通过DMA被放到了NIC buffer中.
  2. tranferTo()方法返回时,需要从内核态切换到用户态。

即使这相对于用传统方式实现的例子有了一些提升,比如,上下文切换次数从四次降到了两次,数据需要被复制三次,而不是四次了。但是,这还不是zero-copy。要进一步降低开销,需要网络接口直接一些特定的操作。从Linux内核2.4之后,socket buffer descriptor就被修改了,来适应这种需求。这个修改,不仅降低了上下文切换的次数,而且消除了数据在被复制时,涉及到的CPU消耗:

  1. transferTo()方法让数据通过DMA被复制内核读缓冲区
  2. 没有数据会被复制到socket buffer。socket buffer只会收到一个descriptor,它包含了数据的位置和长度信息。DMA直接将数据从内核读缓冲区复制到NIC Buffer中,因此,消除掉了CPU消耗。

性能比较

我们在一个内核为2.6的Linux操作系统上,运行了一些测试,最终得出了下面的结果:


我们可以看到,使用transferTo()实现的这种方式,相对于用传统方式实现,只需要大约65%的时间。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容