先把磁盘文件拷贝到 PageCache 上,再拷贝到进程中

简介: PageCache通过缓存磁盘数据到内存,提升读取性能,并支持预读和合并IO。但传输大文件时易占满缓存,影响小文件性能,此时应使用异步IO+直接IO,避免额外拷贝与缓存污染,尤其适用于高并发场景。

回顾上文中,你会发现,读取文件时,是先把磁盘文件拷贝到 PageCache 上,再拷贝到进程中。为什么这样做呢?有两个原因所致。



由于磁盘比内存的速度慢许多,所以我们应该想办法把读写磁盘替换成读写内存,比如把磁盘中的数据复制到内存中,就可以用读内存替换读磁盘。但是,内存空间远比磁盘要小,内存中注定只能复制一小部分磁盘中的数据。通常,刚被访问的数据在短时间内再次被访问的概率很高。用 PageCache 缓存最近访问的数据,当空间不足时淘汰最久未被访问的缓存(即 LRU 算法)。读磁盘时优先到 PageCache 中找一找,如果数据存在便直接返回,这便大大提升了读磁盘的性能。



而且读取磁盘数据时,需要先找到数据所在的位置,对于机械磁盘来说,就是旋转磁头到数据所在的扇区,再开始顺序读取数据。其中,旋转磁头耗时很长,为了降低它的影响,PageCache 使用了预读功能。也就是说,虽然 read 方法只读取了 0-32KB 的字节,但内核会把其后的 32-64KB 也读取到 PageCache,这后 32KB 读取的成本很低。如果在 32-64KB 淘汰出 PageCache 前,进程读取到它了,收益就非常大。这一讲的传输文件场景中这是必然发生的。



综上可以看到 PageCache 的优点,它在 90% 以上场景下都会提升磁盘性能,但在某些情况下,PageCache 会不起作用,甚至由于多做了一次内存拷贝,造成性能的降低。在这些场景中,使用了 PageCache 的零拷贝也会损失性能。



具体就是在传输大文件的时候。比如,你有很多 GB 级的文件需要传输,每当用户访问这些大文件时,内核就会把它们载入到 PageCache 中,这些大文件很快会把有限的 PageCache 占满。然而,由于文件太大,文件中某一部分内容被再次访问到的概率其实非常低。这带来了 2 个问题:首先,由于 PageCache 长期被大文件占据,热点小文件就无法充分使用 PageCache,它们读起来变慢了;其次,PageCache 中的大文件没有享受到缓存的好处,但却耗费 CPU 多拷贝到 PageCache 一次。所以,高并发场景下,为了防止 PageCache 被大文件占满后不再对小文件产生作用,大文件不应使用 PageCache,进而也不应使用零拷贝技术处理。用看电影来举例的话,就是我只想看前10分钟,就要把整部都下下来,这明显是亏的。而高并发场景处理大文件时,应当使用异步 IO 和直接 IO 来替换零拷贝技术。


异步 IO + 直接 IO

回到开头的例子,当调用 read 方法读取文件时,实际上 read 方法会在磁盘寻址过程中阻塞等待,导致进程无法并发地处理其他任务,如下图所示:也就是在拉数据的过程中,一整个流程下来进程都是阻塞的意思。





异步 IO(异步 IO 既可以处理网络 IO,也可以处理磁盘 IO,这里我们只关注磁盘 IO)可以解决阻塞问题。它把读操作分为两部分,前半部分向内核发起读请求,但不等待数据就位就立刻返回,此时进程可以并发地处理其他任务。当内核将磁盘中的数据拷贝到进程缓冲区后,进程将接收到内核的通知,再去处理数据,这是异步 IO 的后半部分。如下图所示:





从图中可以看到,异步 IO 并没有拷贝到 PageCache 中,这其实是异步 IO 实现上的缺陷。经过 PageCache 的 IO 我们称为缓存 IO,它与虚拟内存系统耦合太紧,导致异步 IO 从诞生起到现在都不支持缓存 IO。绕过 PageCache 的 IO 是个新物种,我们把它称为直接 IO。对于磁盘,异步 IO 只支持直接 IO。



直接 IO 的应用场景并不多,主要有两种:第一,应用程序已经实现了磁盘文件的缓存,不需要 PageCache 再次缓存,引发额外的性能消耗。比如 MySQL 等数据库就使用直接 IO;第二,高并发下传输大文件,我们上文提到过,大文件难以命中 PageCache 缓存,又带来额外的内存拷贝,同时还挤占了小文件使用 PageCache 时需要的内存,因此,这时应该使用直接 IO。



直接 IO 的缺点就是无法享受 PageCache 的好处,也就是内核(IO 调度算法)会试图缓存尽量多的连续 IO 在 PageCache 中,最后合并成一个更大的 IO 再发给磁盘,这样可以减少磁盘的寻址操作;另外,内核也会预读后续的 IO 放在 PageCache 中,减少磁盘操作。这些它都是做不到的


小结

基于用户缓冲区传输文件时,过多的内存拷贝与上下文切换次数会降低性能。零拷贝技术在内核中完成内存拷贝,天然降低了内存拷贝次数。它通过一次系统调用合并了磁盘读取与网络发送两个操作,降低了上下文切换次数。尤其是,由于拷贝在内核中完成,它可以最大化使用 socket 缓冲区的可用空间,从而提高了一次系统调用中处理的数据量,进一步降低了上下文切换次数。


零拷贝技术基于 PageCache,而 PageCache 缓存了最近访问过的数据,提升了访问缓存数据的性能,同时,为了解决机械磁盘寻址慢的问题,它还协助 IO 调度算法实现了 IO 合并与预读(这也是顺序读比随机读性能好的原因),这进一步提升了零拷贝的性能。几乎所有操作系统都支持零拷贝,如果应用场景就是把文件发送到网络中,那么零拷贝确实是个好方法。

Tips:其实这里如果是使用SSD这类固态硬盘(不用旋转磁头),PageCache就没有很大的影响,细节请参照我上一篇的那个SSD的文

不过,零拷贝有一个缺点,就是不允许进程对文件内容作一些加工再发送,比如数据压缩后再发送。另外,当 PageCache 引发负作用时,也不能使用零拷贝,此时可以用异步 IO+ 直接 IO 替换。我们通常会设定一个文件大小阈值,针对大文件使用异步 IO 和直接 IO,而对小文件使用零拷贝

相关文章
|
存储 安全 数据库
|
5月前
|
XML 人工智能 前端开发
程序员必备技能——AI画技术图
AI高效绘图技巧:用Claude+Cursor,3分钟生成可编辑的流程图、架构图。涵盖PlantUML、Mermaid、Graphviz、SVG及draw.io四大方案,全部基于文本绘图,支持手动精修,兼顾效率与质量。
1532 0
程序员必备技能——AI画技术图
|
3月前
|
人工智能 前端开发 Java
AGENTS.md完整落地实践:一份文件翻倍AI编码效率实操指南
在AI编程工具广泛普及的当下,Cursor、Claude Code、Copilot、各类智能体工具各自使用独立规则文件,团队需要维护多套配置,切换工具时重复同步编码规范,AI对项目架构、私有组件、构建流程一无所知,产出代码大量不符合团队标准,反复人工修改大幅拉低开发效率。AGENTS.md作为统一行业标准,完美解决配置碎片化、AI缺少项目上下文的痛点,仅在仓库根目录放置一份Markdown文件,即可让全部AI编码工具统一读取项目规则、架构、构建命令,搭配配套工程脚本,实现「AI读懂项目、写完自动自检、自主验证功能」完整闭环,显著提升AI编码产出质量与整体研发效率。本文结合Spring Boot
586 0
什么是幂等
幂等性指操作执行一次或多次结果一致。读操作(如HTTP GET)不改变数据,天然幂等;写操作(如POST、PUT、DELETE)可能改变状态,需额外机制保障幂等。
|
JavaScript Java Serverless
HarmonyOS5云服务技术分享--云函数创建配置指南
本文详细讲解如何在华为HarmonyOS的AGC云函数服务中创建和配置函数,通过HTTP触发器实现云端代码运行。内容涵盖云函数基础功能、创建步骤(包括命名、配置触发方式与环境)、进阶设置(如环境变量、流量治理、版本管理)及实战操作。同时提供避坑指南,帮助开发者轻松上手,解锁Serverless开发黑科技。
|
10月前
|
Java 数据安全/隐私保护
分享一些Java面向对象的实际案例
Java面向对象案例通过校园管理和电商系统,展现封装、继承、多态的协同:封装保护数据,继承复用代码,多态实现灵活扩展,贴近实际应用,体现OOP核心思想。(238字)
465 0
|
Kubernetes Cloud Native 开发者
alibaba-load-balancer-controller v1.2.0:开启云原生网关开源新篇章!敬请探索!
alibaba-load-balancer-controller v1.2.0:开启云原生网关开源新篇章!敬请探索!
451 61
|
NoSQL Linux 编译器
内核实验(一):使用QEMU+GDB断点调试Linux内核代码
如何配置环境并使用QEMU虚拟机结合GDB进行Linux内核代码的断点调试,包括安装QEMU、交叉编译工具链,编译内核以及通过GDB远程连接进行调试的详细步骤。
1936 1
内核实验(一):使用QEMU+GDB断点调试Linux内核代码
|
Linux 测试技术
使用mdadm工具实现软RAID 5实战案例
文章详细介绍了如何使用mdadm工具在Linux系统中创建和管理软RAID 5设备,包括准备工作、创建RAID 5、格式化文件系统、挂载RAID设备、测试读写速度、备份配置信息、手动下线和添加设备以及禁用和启用RAID设备的完整过程。
2033 0
使用mdadm工具实现软RAID 5实战案例
|
Ubuntu Android开发
安卓系统调试与优化:(一)bootchart 的配置和使用
本文介绍了如何在安卓系统中配置和使用bootchart工具来分析系统启动时间,包括安装工具、设备端启用bootchart、PC端解析数据及分析结果的详细步骤。
1531 0
安卓系统调试与优化:(一)bootchart 的配置和使用

热门文章

最新文章