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

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: 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,而对小文件使用零拷贝

相关文章
|
Kubernetes 开发者 Docker
构建高效微服务架构:Docker与Kubernetes的协同
在当今快速迭代和部署应用程序的背景下,微服务架构已成为企业开发的首选模式。此文章通过深入分析Docker容器化技术和Kubernetes集群管理工具,探讨了如何利用这两者协同工作以构建和维护一个高效的微服务系统。我们将剖析Docker和Kubernetes的核心原理,并展示它们如何简化部署流程、提高系统的可伸缩性和可靠性。本文旨在为开发者提供一套实践指南,帮助其在云原生时代下,构建出既灵活又强大的后端服务。
458 3
|
存储 安全 数据库
|
3月前
|
XML 人工智能 前端开发
程序员必备技能——AI画技术图
AI高效绘图技巧:用Claude+Cursor,3分钟生成可编辑的流程图、架构图。涵盖PlantUML、Mermaid、Graphviz、SVG及draw.io四大方案,全部基于文本绘图,支持手动精修,兼顾效率与质量。
1145 0
程序员必备技能——AI画技术图
|
5月前
|
数据采集 数据挖掘 Python
Python3安装步骤详解(附环境变量配置与验证方法)
Python3是专为Windows设计的Python 3官方安装包,支持数据分析、爬虫、自动化等开发。安装时务必勾选“Add Python to PATH”,推荐以管理员身份运行。安装后通过cmd输入`python --version`验证,并可用IDLE或`.py`文件快速上手编程。(239字)
什么是幂等
幂等性指操作执行一次或多次结果一致。读操作(如HTTP GET)不改变数据,天然幂等;写操作(如POST、PUT、DELETE)可能改变状态,需额外机制保障幂等。
|
JSON 缓存 并行计算
NVIDIA 实现通义千问 Qwen3 的生产级应用集成和部署
阿里巴巴近期开源了通义千问Qwen3大语言模型(LLM),包含两款混合专家模型(MoE)235B-A22B与30B-A3B,以及六款稠密模型(Dense)从0.6B到32B不等。开发者可基于NVIDIA GPU使用TensorRT-LLM、Ollama、SGLang、vLLM等框架高效部署Qwen3系列模型,实现快速词元生成和生产级应用开发。
|
JavaScript Java Serverless
HarmonyOS5云服务技术分享--云函数创建配置指南
本文详细讲解如何在华为HarmonyOS的AGC云函数服务中创建和配置函数,通过HTTP触发器实现云端代码运行。内容涵盖云函数基础功能、创建步骤(包括命名、配置触发方式与环境)、进阶设置(如环境变量、流量治理、版本管理)及实战操作。同时提供避坑指南,帮助开发者轻松上手,解锁Serverless开发黑科技。
|
8月前
|
Java 数据安全/隐私保护
分享一些Java面向对象的实际案例
Java面向对象案例通过校园管理和电商系统,展现封装、继承、多态的协同:封装保护数据,继承复用代码,多态实现灵活扩展,贴近实际应用,体现OOP核心思想。(238字)
408 0
|
Kubernetes Cloud Native 开发者
alibaba-load-balancer-controller v1.2.0:开启云原生网关开源新篇章!敬请探索!
alibaba-load-balancer-controller v1.2.0:开启云原生网关开源新篇章!敬请探索!
420 61
|
NoSQL Linux 编译器
内核实验(一):使用QEMU+GDB断点调试Linux内核代码
如何配置环境并使用QEMU虚拟机结合GDB进行Linux内核代码的断点调试,包括安装QEMU、交叉编译工具链,编译内核以及通过GDB远程连接进行调试的详细步骤。
1867 1
内核实验(一):使用QEMU+GDB断点调试Linux内核代码

热门文章

最新文章