首页
学习
活动
专区
工具
TVP
发布

IO操作全解之Zero-copy

上一篇文章大概讲解了

Kafka的大概流程

。kafka高性能是由于其内容存储上用到了两个关键技术点:pageCache,Sendfile.本文就来深入聊聊这个技术点的来龙去脉。为了深入浅出的讲解,本文分为以下三篇文章讲解,这样就能把IO的底层以及上层应用串联起来:

1 高性能IO的基础知识(本文)

2 Java API 如何使用相关技术

3 聊聊NIO相关知识

1.1 存储器的层级结构剖析,先看下图

由于存储介质的速度与成本是相悖的,现代计算机的存储结构呈现为金字塔型。越往塔顶,存取效率越高、但成本也越高,容量也就越小。由于程序都是访问局部数据,这种缓存结构也适合运行效率是很高的。上图可见缓存是无论不在,从存储器的层次结构以及计算机对数据的处理方式来看,上层一般作为下层的Cache层来使用(广义上的Cache)。比如寄存器缓存CPU Cache的数据,CPU Cache L1~L3层视具体实现彼此缓存或直接缓存内存的数据,而内存往往缓存来自本地磁盘的数据。接下来焦点继续缩放,只关注与磁盘存储这块。

1.2 pageCache 是什么

广义上的Cache同步有两种方式:Write Through(写穿,意思是跟底层设备一块刷新数据,这种能保证数据强一致性,但是会牺牲性能), Write Back(写回,意思是把数据写入Cache就算完事了,底层设备数刷新是个异步操作,这种可能会丢数据,但是能把性能发挥最大)。 pageCache 是磁盘数据在内存(是内核空间的内存)中的缓存见下图。 这层缓存提升了读写效率,但是如果写入数据,采用了何种数据同步方式,应当根据业务场景而定。可靠性跟高性能是一种折中的设计,就像Kafka的设计,充分利用PageCache带来的读写优势,用多副本来提升可靠性. 所以如何设计是根据业务场景来定制的。

1.3 传统IO数据如何传输

传统IO是一种Buffer IO, 我们读文件发给网络那头某个客户端,经常是这个样子的:Open 一个File, read出来,吧啦啦啦一顿操作,再write出去。

那么这顿操作的数据流就是下图的样子。当应用程序发起read操作,如果文件未被加载过,那么 DMA把磁盘文件数据copy 到pageCache,然后CPU把pageCache中的数据copy 到用户空间的应用缓冲区。这时候码农才有对数据进行加工的权利。加工完之后程序发起write操作,CPU把应用程序缓冲区的内容copy 到内核缓冲区(如果写的是磁盘就是pageCahe,如果写的是网络就是Socket Buffer,本例子说的是写网络),最后由DMA 把数据Copy 给网卡。

1.4 zero-copy 是什么

上图可见上层应用简单的两个函数,read,write ,系统进行了大量的数据流转工作。其中内核与户空间的数据copy 就是从一块内存地址复制到另外一块,这完全就是浪费了内存资源,也浪费了CPU资源,让CPU干这种无脑体力活,真叫浪费。既然浪费linux 就引入了一种zero-copy技术来把CPU从这种坑中解救出来。来看下维基百科对zero-copy的描述:

零拷贝描述的是CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务(也就是不在内核与用户空间进行数据copy),这通常用于通过网络传输一个文件时以减少CPU周期和内存带宽。

zero-copy好处很显然:

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

减少内存带宽的占用

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

那么zero-copy是怎么实现的呢。零拷贝实际的实现并没有真正的标准,取决于操作系统如何实现这一点。零拷贝完全依赖于操作系统。操作系统支持,就有;不支持,就没有。那么linux具体怎么实现的呢?这就引入了另外两个技术点:mmap,sendfile。我们接下来仔细剖析这两个技术点。这两种技术都是围绕如何把用户空间与内核之间的CPU copy 给消灭掉。

1.5 mmap 实现 zero-copy

什么是mmap,这种技术直接把pageCache映射到了用户态的内核空间这种就省去了CPU copy 部分。在 Linux 中,减少拷贝次数的一种方法是调用 mmap() 来代替调用 read,程序调用如下:1 tmp_buf = mmap(file, len); 2 write(socket, tmp_buf, len);

首先,应用程序调用了 mmap() 之后,数据会先通过 DMA 拷贝到操作系统内核的缓冲区中去。接着,应用程序跟操作系统共享这个缓冲区,这样,操作系统内核和应用程序存储空间就不需要再进行任何的数据拷贝操作,这时用户可以直接对数据进行加工。应用程序调用了 write() 之后,操作系统内核将数据从原来的内核缓冲区中拷贝到与 socket 相关的内核缓冲区中,此处两个内核区的缓冲区间的copy是CPU copy,从操作系统角度这已经是zero-copy因为数据没有在内核与用户空间进行copy。接下来,数据从内核 socket 缓冲区拷贝到协议引擎中去。当有大量数据要传输,这种操作会有一个比较好的效率。凡是都有两面性,mmap在使用的时候要注意文件共享带来的问题。此处不对mmap使用细节进行讨论

1.6 sendfile 实现 zero-copy

先来遐想一个场景,如果1.3中应用程序仅仅是读了文件,原封不动的发了出去,那么系统可以直接把上图中页缓存中数据给到Socket缓冲区就行了,甚至直接不要socket 缓冲区,直接把页缓冲区的内容发给网卡。那么linux 系统低级版本就是前一种做法,高级版本支持了后一种做法。分别上两张图来直管感受下:

1.6.1 普通sendFile 数据传输:

页缓冲-->socket 缓冲区,用了一次cpu copy。

1.6.2 高级sendFile 数据传输:

上图中没有数据拷贝到socket缓冲区。取而代之的是只有相应的描述符信息会被拷贝到相应的socket缓冲区当中。该描述符包含了两方面的信息:1)页缓存的内存地址;2)页缓存的偏移量。DMA gather copy根据socket缓冲区中描述符提供的位置和偏移量信息直接将内核空间缓冲区中的数据拷贝到协议引擎上。 这种高级操作是需要硬件支持的。

有同学可能会疑惑1.5 跟1.6.1 难道不是一样的么,从数据流转的角度是一样的,不一样的地方是用户空间与内核空间的切换次数上。1.5 每调用一次函数就切换两次,一共四次。例如:mmap,先是用户态--->内核态,把数据从磁盘copy进内存,完事函数返回,返回就意味着由内核态--->用户态。应用程序可以该干嘛干嘛了。 而1.6.1和1.6.2 都只有两次转换,因为应用程序仅仅发出一个sendfile 函数。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181230G0U59L00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券