零拷贝技术

零拷贝

  • 如何实现文件传输功能?
    • 方法的不足
      • 上下文切换
      • 多次内存拷贝
  • 零拷贝
    • 降低上下文切换频率
    • 减少内存拷贝次数
  • PageCache 磁盘高速缓存
  • 异步 IO
  • 直接 IO
    • 应用场景
    • 直接 IO 的不足
  • 方法论
    • 文件传输的性能优化思路
  • 案例
  • 疑惑点
  • 思考题
    • 一篇介绍IO模型的文章

如何实现文件传输功能?

服务器提供文件传输功能,首先从磁盘读取文件,然后通过网络协议发送给客户端。最直接的办法是根据客户端的请求从磁盘上找到文件位置,然后从磁盘把部分文件(一般文件比较大时,需要对文件进行切分)读入到缓冲区,然后再通过网络把数据发送给客户端。

方法的不足

上下文切换

该方案在一次收发过程中涉及到 4 次用户态和内核态的上下文切换,没处理缓冲区大小的数据需要一次 read 调用和一次 write 调用,每次调用都需要从用户态切换到内核态,然后等内核态完成任务后,再切回用户态。因为文件比较大,读取次数比较多,所以上下文切换的成本不容小觑

多次内存拷贝

磁盘 -> PageCache
PageCache -> 用户缓冲区
用户缓冲区 -> Socket 缓冲区
Socket 缓冲区 -> 网卡
因为涉及到多次内存拷贝,消耗过多的 CPU 资源,降低系统并发处理能力
想要优化传输文件的性能,需要从降低上下文切换的频率和内存拷贝次数入手

零拷贝

降低上下文切换频率

读取磁盘文件的上下文切换是一定会做的,因为读取磁盘和操作网卡都是由操作系统内核完成。所以我们在执行 read 或 write 这种系统调用时,一定会经过 2 次上下文切换:先从用户态切换到内核态,当内核态任务完成后,再切换回用户态交由进程代码执行
所以,要想降低上下文切换频率的要点就是减少系统调用的次数。解决办法是把 read 和 write 两次系统调用合并为一次(可以通过 sendfile 一次系统调用完成),在内核态中完成磁盘与网卡的数据交换操作

减少内存拷贝次数

一次收发过程中有两次与物理设备相关的内存拷贝是必不可少的:把磁盘的数据拷贝到内存;把内存的数据拷贝到网卡。而与用户缓冲区相关的内存拷贝不是必须的

综上所述,可以在内核读取文件后,直接把 PageCache 中的数据拷贝到 socket 缓冲区中,这样就只有 2 次上下文切换 和 3 次内存拷贝。如果网卡支持 SG-DMA 技术,还可以把拷贝到 socket 缓冲区的步骤给省略掉

PageCache 磁盘高速缓存

根据时间局部性原理(刚被访问到的数据在短时间被再次访问的概率高),通常将最近访问的数据放到 PageCache 中,当空间不足时通过 LRU 算法或变种算法淘汰最久未被访问的数据
PageCache 还提供预读功能

但 PageChache 不适应传输大文件的场景,大文件容易把 PageCache 占满,而且由于文件太大,文件中某一个部分被再次访问的概率低。这样会导致大文件在 PageCache 中没有享受到缓存的优势,同时也因为 PageCache 被大文件占据,影响其他热点小文件的缓存

异步 IO

异步 IO 可以把读操作分为两部分,前半部分向内核发起读请求,但不用等待数据就位就返回,然后可以继续处理其他任务。当内核把磁盘中的数据拷贝到进程缓冲区后,会通知进程去处理数据。异步 IO 是不会阻塞用户进程的
对于磁盘,异步 IO 只支持直接 IO

直接 IO

直接 IO 是应用程序绕过 PageCache,即不经过内核缓冲区,直接访问磁盘中的数据,从而减少了内核缓存与用户程序之间的数据拷贝

应用场景

  • 应用程序已经自己实现了磁盘文件的缓存,不需要 PageCache 再次进行缓存,引发额外的性能消耗
  • 高并发下传输大文件,因为大文件难以命中 PageCache 缓存,又会影响其他热点小文件的缓存

直接 IO 的不足

因为直接 IO 不适用 PageCache 缓存,所以享受不到内核针对 PageCache 做的一些优化,比如内核会试图缓存更多的连续 IO 在 PageCache 中,然后合并成一个更大的 IO 后发给磁盘,可以减少磁盘的寻址操作;另外,内核还会进行数据的预读,把数据缓存到 PageCache 中,较少磁盘操作

方法论

  • 大文件交给异步 IO 和直接 IO 处理,小文件交给零拷贝处理

文件传输的性能优化思路

  • 减少磁盘的工作量(PageCache 技术)
  • 提高内存的利用率(零拷贝)
  • 较少 CPU 的工作量(直接 IO)

案例

Kafka 的高性能的原因就包括使用了零拷贝技术和 PageCache 缓存

疑惑点

  • 在介绍零拷贝时,降低上下文切换频率提到的将 read 和 write 两次系统调用合并为一次是通过 sendfile 实现吗?
  • 异步 IO 没有使用 PageCache,因为与虚拟内存系统耦合太紧,这块没有看明白

思考题

异步 IO 一定不会阻塞进程吗?如果阻塞了进程,该如何解决?
这个问题我觉得要看怎么定义异步 IO
Posix 对异步 IO 的定义为:异步 IO 操作不引起请求进程阻塞。老师在文中对异步 IO 的定义也类似,当内核把磁盘中的数据拷贝到进程缓冲区后,会通知进程去处理数据,所以按照这样的定义,异步 IO 不会阻塞进程

《UNIX网络编程》这本书把 IO 模型分成了5类
1.阻塞 IO
2.非阻塞 IO
3.IO 复用(select 和 poll)
4.信号驱动
5.异步 IO(Posix.1 的 aio 系统函数)

一篇介绍IO模型的文章

IO模型

如果把信号驱动的 IO 模型也看成异步 IO,因为用户进程在调用 sigaction 后,会继续执行其他任务,这里是非阻塞的,内核会在数据准备好时通知用户进程,然后由用户进程发起 recvfrom 的系统调用,把数据从内核缓冲区拷贝到用户空间,此时用户进程是阻塞的

所以还是看如何定义异步 IO,至于那些伪异步 IO,怎么解决,我觉得解决办法就一个,就是等内核把数据准备好后,自己把数据从内核缓冲区复制到用户空间,然后通知用户进程进行数据的处理

参考资料

《系统性能调优必知必会》专栏-极客时间

你可能感兴趣的:(Linux)