1. 先弄清楚这三者到底在"吵架"什么
内存、CPU、磁盘这三样东西,只要你在电脑前坐超过三年,就一定被它们三兄弟联合教训过。任务管理器里某个进程莫名其妙吃掉几个G内存,CPU占用率飙到100%但机器卡得连鼠标都拖不动,或者磁盘指示灯长亮不灭、打开一个文件夹要等半分钟——这些现象背后,其实都是同一个故事的不同章节:计算速度极快的那一方,被迫等待速度极慢的那一方,而中间那个"和事佬"还经常不够用。
我写这篇东西的目的很直接:把CPU、内存、磁盘的交互链条从头到尾捋一遍,讲清楚数据是怎么在这三者之间流动的,为什么要有缓存,为什么要有虚拟内存,为什么磁盘满了会连带把数据库搞崩。适合谁看?写过代码但对底层只有模糊印象的后端同学,运维过程中只会看任务管理器数字的运维同学,还有那些被"内存溢出""磁盘爆满"折腾过但说不清原理的人。看完之后你至少能做到一件事:看到一个性能现象,能大致判断是哪一层出了问题,而不是无脑重启或者加内存。
先说一句总纲性质的话:现代计算机的性能设计,本质上是一场关于"速度差"的妥协艺术。CPU快得像光速,内存慢一个数量级,磁盘再慢两到三个数量级。如果让CPU每次都直接找磁盘要数据,那我们现在用的就不是电脑,是一台昂贵的电子闹钟。所以整个体系的核心思路就是——用快的存储去"挡"住对慢存储的访问,挡不住的再想办法提前搬、批量搬、异步搬。
1.1 用厨房来类比:灶台、操作台和仓库
我把这套体系类比成一个厨房。CPU是厨师,他的手速极快,一秒能颠十下锅;内存是灶台旁边的操作台,切好的菜、调料都摆在这上面,伸手就能拿;磁盘是楼下的大仓库,东西齐全但拿一次要走楼梯、签字、再走回来。
厨师做一道菜,需要葱姜蒜。理想情况是这些东西已经在操作台上,他伸手就拿。如果操作台没有,他要么自己下楼去仓库取——这一趟来回,够他炒糊三盘菜了;要么喊一个跑腿的小工去取,自己继续处理手头的食材。这个"跑腿的小工"就是DMA控制器,我们后面会专门讲。
操作台的大小就是内存容量。操作台越大,能同时摆的食材越多,厨师下楼取东西的次数就越少,整体出菜速度就越快。但操作台不是越大越好,因为越大越贵,而且厨师找东西的速度也会受一点影响(寻址开销)。这就是为什么内存容量的选择永远是"够用+留余量",而不是无限加。
仓库则是持久化存储。它的特点是断电不丢东西,但速度慢。所有你要长期保存的数据,最终都得落到仓库里。厨师下班前,得把今天用过的、以后还要用的东西写进仓库账本,这就是磁盘写入。
理解了这套类比,后面所有的技术细节都只是往这个框架里填肉。缓存是操作台上再放的一个小托盘,只放最常用的几样;虚拟内存是当操作台不够用时,临时把一部分食材挪到仓库,腾出地方;缺页中断就是厨师伸手发现托盘里没东西,被迫停下来等。
1.2 存储层次的本质是速度和容量的跷跷板
计算机存储是一个典型的金字塔结构,越往上越快、越小、越贵,越往下越慢、越大、越便宜。这个结构不是谁拍脑袋设计的,而是被物理规律逼出来的:越靠近CPU的存储,制造工艺越苛刻,单位成本越高,所以只能用很小的容量换取极高的速度。
| 层级 | 典型容量 | 访问延迟(数量级) | 谁来管理 |
|---|---|---|---|
| 寄存器 | 几百字节 | 亚纳秒级 | 编译器/CPU |
| L1 缓存 | 32~64 KB/核 | 1 纳秒级 | CPU 硬件 |
| L2 缓存 | 256 KB~2 MB/核 | 数纳秒 | CPU 硬件 |
| L3 缓存 | 8~64 MB | 十几到几十纳秒 | CPU 硬件 |
| 内存 | 8~512 GB | 80~120 纳秒 | 操作系统 |
| 固态硬盘 | 数百 GB~数 TB | 微秒级 | 操作系统/固件 |
| 机械硬盘 | 数 TB | 毫秒级 | 操作系统/固件 |
这张表里最该被记住的是量级差距。从内存到固态硬盘,延迟差了大概两个数量级;到机械硬盘,差了四个数量级。也就是说,CPU等一次机械硬盘的随机读,相当于它执行了几十万条指令。这个比例关系解释了几乎所有性能优化的方向:减少慢速存储的访问次数,比优化计算逻辑更有价值。
顺带说一个热词里经常出现的现象:为什么有些机器"GPU、CPU、内存占用都不高但就是卡"。这类问题很大概率卡在I/O等待上——任务管理器只显示CPU的忙碌时间,不显示CPU在等待I/O时被阻塞的时间。Windows的任务管理器里看"磁盘"那一栏的"响应时间",如果长期在几百毫秒以上,那基本就是存储层在拖后腿,跟CPU和内存没关系。
1.3 谁在指挥这场交互:操作系统是总调度
CPU、内存、磁盘三者本身并不会自动协作,它们之间所有交互都由操作系统内核调度。内核提供三套抽象:进程拿到的是虚拟地址空间,文件系统拿到的是块设备,CPU拿到的是页表。这三套抽象之间的转换,就是数据流动的全部秘密。
进程读写内存时用的是虚拟地址,内核通过页表把它翻译成物理地址,再由内存管理单元(MMU)落到真正的内存条上。进程读写文件时用的是文件描述符,内核通过文件系统把它翻译成磁盘块号,再交给块设备驱动,最后通过总线送到磁盘控制器。
这里有个关键点很多人忽略:进程永远不能直接访问物理内存,也不能直接访问磁盘。所有访问都必须经过内核翻译。这个"翻译层"的存在既带来了安全隔离,也带来了性能开销。很多性能优化技术(比如大页内存、直接I/O、零拷贝)本质上都是在削减翻译层数或者降低单次翻译成本。
2. CPU与内存的交互:从寄存器到缓存行
CPU和内存的关系,是三者里最紧密、也最容易被误解的一段。很多人以为CPU是从内存直接读变量的,其实绝大多数情况下不是。CPU真正读写的是缓存,只有当缓存里找不到时,才会去内存取。
现代CPU核心和内存之间隔了三层缓存,而且数据不是按字节搬运的,是按"缓存行"搬运的,主流架构一行是64字节。这意味着一件听起来有点荒唐的事:你只读了1个字节,硬件却顺手把周围63个字节也搬进了缓存。这个设计看起来浪费,但它恰恰是性能的关键来源。
2.1 一条简单赋值语句在硬件上发生了什么
写一句counter = counter + 1;,编译器会翻译成类似"从内存地址加载到寄存器、寄存器加一、写回内存"的指令序列。但硬件执行时,第一步的"从内存加载"会被缓存接管:CPU先去L1找,找不到去L2,再找不到去L3,最后才真正向内存控制器发请求。
这个过程有个专业名字叫缓存命中率。L1命中,几个时钟周期搞定;L3命中,几十个周期;真的要去内存,上百个周期。所以同一段代码,数据布局不同,性能能差出好几倍。
我在实际项目里见过一个典型例子:一个统计程序遍历二维数组,行优先和列优先两种写法,逻辑完全一样,运行时间差了接近4倍。原因就是列优先遍历破坏了空间局部性,每一行数据都要重新从内存搬,缓存几乎全miss。这类问题不看硬件层根本解释不通,只知道"慢"。
2.2 缓存行、局部性和伪共享
缓存行的存在带来了两个直接后果。好的方面是空间局部性收益:顺序访问数组时,一次搬运能让后面63个字节全部命中,实际带宽被放大。坏的方面是伪共享:两个不同变量如果落在同一个缓存行里,被不同CPU核心分别修改,就会导致缓存行在两个核心之间反复失效和同步。
伪共享的典型现象是多线程程序加了线程数反而变慢,而且CPU占用不高但吞吐上不去。排查手段是看缓存行填充,常见做法是在变量之间插入填充字节,让它们落在不同的缓存行里。这个技巧在高频交易、游戏引擎这类对延迟极敏感的领域用得很多。
提示:伪共享是"看不见的性能杀手"。如果你的多线程程序扩展性很差,先怀疑共享数据的布局,再怀疑锁。
2.3 内存屏障和可见性:为什么需要volatile
缓存带来了一个新的问题:每个核心都有自己的一份数据副本,那么一个核心改了数据,另一个核心什么时候能看到?答案是——不保证立刻看到。硬件只在缓存一致性协议允许的时机同步,而这个时机对上层是透明的。
这就是Java内存模型(JVM内存模型)要解决的问题。Java里的volatile关键字,本质上是告诉编译器和CPU:这个变量的读写不能被优化掉,并且要插入内存屏障,强制让其他核心看到最新值。没有它,一个线程写的标志位可能永远停在另一个线程的缓存里,程序就这么死循环下去。
理解这一点之后再看JVM的happens-before规则,就不会觉得是死记硬背的规定了。它其实是把硬件层的缓存同步行为,用一套语言规范表达出来,让开发者不用直接面对CPU的乱序执行和缓存刷新。
2.4 内存频率和时序到底怎么算
买内存条时会看到"3200MHz CL16"这类参数。这两个数字的含义值得说清楚,因为它们直接关系到内存的实际带宽和延迟。
频率决定带宽。DDR是双倍数据率,所以标称3200MHz的实际时钟是1600MHz,每个时钟周期传输两次数据。带宽的计算公式是:
带宽(GB/s) = 频率(MHz) × 位宽(bit) ÷ 8 ÷ 1000以双通道DDR4-3200为例,位宽是64位×2=128位:
3200 × 128 ÷ 8 ÷ 1000 = 51.2 GB/s时序决定延迟。CL16的意思是列地址选通延迟为16个时钟周期。换算成绝对时间:
16 ÷ 1600MHz = 10 纳秒这个10纳秒就是一次内存访问的CAS延迟。所以你会看到一个反直觉的现象:高频内存如果时序放松太多,实际延迟可能比低频紧时序的还差。选内存时不能只看频率,得把两个参数一起看。这也是为什么很多玩家会花时间手调时序——在特定频率下把时序压到能稳定运行的最低值,能实打实降低延迟。
3. 内存与磁盘的交互:虚拟内存这套障眼法
如果说CPU和内存的交互是"硬件层的默契",那内存和磁盘的交互就是"操作系统层的魔术"。这套魔术的名字叫虚拟内存,它的核心目标只有一个:让每个进程都以为自己独占了一整块巨大的、连续的内存,而实际上物理内存可能只有它的几分之一大。
虚拟内存不是新概念,从1960年代就开始用了,但很多人对它的理解停留在"内存不够就用硬盘凑",这其实只说对了一半。虚拟内存更重要的意义是隔离和抽象:每个进程有独立的地址空间,互相看不到也改不了,程序崩溃不会连坐,这才有了现代操作系统的稳定性。
3.1 虚拟地址到物理地址的映射过程
进程里一个指针的值,比如0x7f4a2c001000,这是虚拟地址。CPU执行访问时,MMU拿这个地址去查页表,翻译成物理地址,再去访问内存条。页表本身也存在内存里,所以一次地址翻译可能变成两次内存访问(一次查页表,一次取数据)。
为了减少这个开销,硬件里加了转址旁路缓存(TLB),专门缓存最近用过的地址映射。TLB命中就一步到位,TLB未命中才去查页表。这也是为什么程序的内存访问模式要尽量集中——访问的页越少,TLB越容易命中。
页的大小通常是4KB,大页是2MB或1GB。大页的意义就在于:同样的内存范围,用2MB页只需要一张页表项,TLB能覆盖的内存范围大512倍。数据库这类需要频繁访问大块内存的应用,开大页往往能拿到可观的性能提升。
3.2 缺页中断:一次被迫停下来的等待
当进程访问的虚拟地址页不在物理内存里时,硬件会触发缺页中断,CPU暂停当前指令,切到内核的缺页处理程序。内核会去磁盘上找这个页,读进内存,更新页表,然后返回,让CPU重新执行刚才那条指令。
这一来一回,就是从内存级延迟跳到磁盘级延迟。固态硬盘大概几十微秒,机械硬盘就是几毫秒。如果程序频繁缺页,表现就是CPU占用不高、磁盘狂转、程序卡顿——这正是任务管理器里"磁盘100%"但CPU很闲的典型画面。
这类问题的排查工具在Linux下是vmstat和pidstat:
# 每秒输出一次,重点看 si/so(换入换出)和 majflt(主缺页) vmstat 1 # 按进程看主缺页次数 pidstat -r -p <pid> 1如果si/so长期非零,或者主缺页次数持续增长,说明物理内存真的不够了,进程在磁盘和内存之间反复倒腾。这个状态在运维圈里有个形象的说法叫"抖动",性能会呈断崖式下跌。
3.3 交换分区和内存压缩,怎么选
内存不够时,操作系统有两条路:把不常用的页写到磁盘(交换),或者把多个页压缩后塞在内存里(内存压缩)。
交换的优点是能腾出大量空间,缺点是慢——写出去再读回来都是磁盘级操作。Windows和Linux都默认开启交换,但在固态硬盘上频繁交换会加速磨损,在机械硬盘上则会让系统卡到无法忍受。
内存压缩的思路不同,它把不活跃的页压缩后留在内存里,需要时解压还原。压缩比通常能到2:1到3:1,优点是快,缺点是消耗CPU。Windows 10和Windows 11默认开启内存压缩,对应的是任务管理器里"内存"那一栏的"已压缩"数字。如果发现压缩内存占比很高,同时又觉得CPU占用上来了,可以权衡是否关闭——热词里"win10关闭内存压缩""win11内存占用过高怎么解决"这两个搜索词,背后就是这个问题。
我的建议是:物理内存16GB以下、还开着机械硬盘的机器,保留内存压缩;物理内存32GB以上、固态硬盘的机器,压缩带来的收益有限,可以考虑关掉换取一点CPU。但要注意,关闭压缩不等于内存够用,只是把压力从CPU转移回硬盘。
3.4 脏页和写回:什么时候数据才真的落盘
进程调用write()写文件时,数据通常只是被复制到内核的页缓存里,标记为"脏页",并没有立刻写到磁盘。内核会在合适的时机批量刷盘,这个机制叫写回。好处是合并小写入、减少磁盘次数,坏处是断电时可能丢数据。
控制写回行为的几个关键参数在Linux下是:
# 脏页占总内存的比例,超过就触发后台刷盘 vm.dirty_background_ratio = 10 # 脏页硬上限,超过就阻塞写入 vm.dirty_ratio = 20 # 脏页最长驻留时间 vm.dirty_expire_centisecs = 3000这几个值调小会让数据更快落盘、掉电更安全,但磁盘写入次数增加;调大则相反。数据库服务器上通常会把它们调小,因为数据丢失的代价远高于磁盘性能损耗。
顺带解答一个热词里的困惑:"word保存显示磁盘已满"。这个报错的真实原因往往不是磁盘真的满了,而是临时目录所在的分区满了,或者磁盘配额用尽。Word在保存时会先写临时文件再改名,临时目录空间不足就会报这个错。排查时要看的是%TEMP%指向哪个盘,而不是文档所在的那个盘。
4. CPU与磁盘的交互:DMA这条绕开CPU的捷径
按前面的逻辑,磁盘数据要进内存,似乎应该由CPU一条条搬运。如果真是这样,那CPU会被I/O彻底拖死。早期计算机确实这么干,叫PIO模式,CPU要盯着磁盘控制器的状态寄存器,一个字节一个字节地读进来。这种方式下,CPU几乎不能做别的事。
DMA(直接内存访问)就是为解决这个问题而生的。它让外设控制器直接和内存打交道,数据搬运不经过CPU的寄存器,CPU只需要在开始和结束时各介入一次。这套机制是今天所有高速I/O的基础。
4.1 PIO和DMA的差别有多大
用数字说话:一次磁盘读取假设传输4KB数据。PIO模式下,CPU要执行几千次搬字节的指令,加上轮询状态的开销,CPU可能被占满几十微秒。DMA模式下,CPU只发一条命令,然后就可以去执行别的线程,等中断通知结果就行,占用时间降到微秒以下。
这个差距在多任务环境下会被放大。一台服务器同时跑几百个线程,每个线程都在读文件,如果都用PIO,CPU的时间全花在搬字节上,真正做计算的份额少得可怜。DMA的价值不在于单次传输更快,而在于把CPU从搬运工的角色里解放出来。
4.2 从磁盘到进程内存的完整数据链路
现在把整个流程串起来。一个进程调用read()读文件,内核做的事情大致是:
- 查页缓存,如果数据已经在缓存里,直接从内存复制到进程缓冲区,全程不碰磁盘。
- 如果不在缓存里,向块设备层发起读请求,进程进入睡眠。
- 块设备驱动把请求转成磁盘命令,交给磁盘控制器。
- 磁盘控制器用DMA把数据写进内核指定的内存缓冲区。
- 传输完成,磁盘发中断,CPU执行中断处理程序,唤醒睡眠的进程。
- 内核把数据从页缓存复制到进程的用户空间缓冲区,
read()返回。
这条链路上有两处值得注意的性能点。一处是第1步的页缓存命中,命中就是内存级操作,未命中就是磁盘级操作,差距三个数量级。另一处是第6步的数据复制,数据在内核空间和用户空间之间被搬了一次,这是纯粹的额外开销。
4.3 零拷贝和mmap为什么能省时间
针对第6步的复制开销,就有了mmap和零拷贝技术。mmap的做法是把文件映射到进程的虚拟地址空间,进程读写这块内存就相当于读写文件,页缓存的页直接被映射到用户空间,省掉了"内核缓冲区到用户缓冲区"这一趟复制。
零拷贝(比如Linux的sendfile)更进一步,数据从磁盘读进页缓存后,直接由网络协议栈从页缓存取走发出去,全程不经过用户空间。对文件服务器、消息队列这类应用,收益非常明显。
不过要注意,这些技术不是万能药。mmap在小文件、频繁变更的文件上可能因为缺页中断反而更慢;零拷贝则受限于具体的使用场景。选型时得看数据规模和访问模式,不能看到"零拷贝"三个字就往上套。
5. 真实场景排查:从现象反推是哪一层出了问题
讲完原理,最实用的部分来了。性能问题排查的核心思路是:先定位是哪一层成为瓶颈,再在这一层里找具体原因。下面按现象分类,给出我实际处理过的几个典型案例。
5.1 CPU高、磁盘低:先看是谁在烧CPU
CPU占用高但磁盘很闲,说明瓶颈在计算层。这时候分两种情况:如果是某个业务进程占用高,通常是代码问题,比如死循环、正则回溯爆炸、频繁GC;如果不是业务进程,就要看系统进程。
热词里出现的几个进程名都很有代表性。"Antimalware Service Executable"是Windows Defender的扫描进程,它在全盘扫描时会持续吃CPU,可以在排除目录里把开发目录、编译产物目录加进去。"服务主机DCOM占用CPU高"通常是某个组件在反复请求DCOM服务,需要具体定位是哪个子进程。"aceguardclient占用CPU很高"这类防护客户端的问题,基本只能靠版本更新或换配置解决。
排查手段上,Linux下用top按CPU排序,再pidstat -u -p <pid> 1看线程级占用;找到具体线程后用jstack或perf定位到代码位置。
5.2 内存高、CPU不高、磁盘偶尔转:怀疑漏和抖动
内存持续增长不下降,第一种可能是内存泄漏,第二种可能是缓存没有上限。区分的方法是看进程重启后是否恢复正常:重启就好转的是泄漏,重启后慢慢又涨回去的可能是缓存策略问题。
如果同时伴随磁盘轻量但持续的读写,那就可能是虚拟内存抖动的前兆。这时候要算一笔账:物理内存总量减去已经确认的常驻需求,剩下的余量够不够应对峰值。不够就得扩容或者优化数据结构的驻留时间。
热词里"wechatappex占用内存过高""xssfworkbook内存溢出"都属于这一类。前者是客户端进程的缓存管理问题,后者是典型的API使用不当——用XSSFWorkbook处理大Excel文件时会一次性把所有单元格对象驻留内存,换个流式写入库或者改用CSV,内存占用能降一个数量级。
5.3 磁盘爆满:MySQL三大故障场景里最要命的一个
磁盘爆满在数据库运维里属于一等事故,因为它会导致写入失败、事务回滚、主从延迟,甚至实例直接不可用。热词里"mysql 三大故障场景:磁盘爆满"这个说法很准确,另外两个通常是连接打满和慢查询堆积。
磁盘爆满的常见原因有这么几类:binlog没有定期清理,慢查询日志无限增长,临时表写爆磁盘,还有大事务产生的undo日志堆积。处理顺序是先用du找出大目录,确认是哪一类,再针对性清理。
# 找出根目录下占用最大的前20个目录 du -h --max-depth=1 / | sort -rh | head -20 # 查看binlog大小 ls -lh /var/lib/mysql/ | grep binlog清理binlog要用PURGE BINARY LOGS命令,不能直接rm,否则索引文件会对不上。这是踩过的坑,直接删文件会导致主从复制出错。
注意:生产环境磁盘用量建议保持在70%以下,超过85%就该告警。磁盘不像内存有交换空间兜底,写不进去就是写不进去。
5.4 一张表看完常见现象和对应层级
| 现象 | 最可能卡在哪一层 | 先看什么指标 |
|---|---|---|
| CPU 100%,磁盘空闲 | 计算层 | 进程级CPU占用、线程栈 |
| CPU 30%,磁盘响应时间上千毫秒 | 存储层 | 磁盘队列长度、IOPS |
| 内存持续上涨不回落 | 内存层 | 进程RSS、堆内存曲线 |
| 系统整体卡顿,硬盘灯长亮 | 虚拟内存抖动 | 换页次数、主缺页率 |
| 程序响应波动大,无明显峰值 | 缓存命中率低 | 缓存命中率、TLB miss |
| 大文件写入后系统卡几分钟 | 脏页刷盘 | 脏页比例、写回阈值 |
这张表不是绝对的,但能覆盖八成场景。用它的方法是:先量指标,再对现象,最后才改配置。顺序反了就容易做无用功。
6. 自己动手观测一次完整的三层交互
原理讲再多,不如自己看一次真实数据。下面这套操作在Linux上可以直接复现,能让你亲眼看到CPU、内存、磁盘是怎么联动起来的。
6.1 准备观测环境
需要三个终端窗口。第一个跑监控,第二个跑测试程序,第三个做对比实验。监控命令用vmstat和iostat组合:
# 终端1:每2秒输出一次,观察CPU、内存、换页 vmstat 2 # 同时另一个窗口看磁盘 iostat -x 2vmstat输出里重点关注这几列:r是运行队列长度,大于CPU核数说明有排队;si/so是换入换出,非零就要警惕;us/sy/wa分别是用户态、内核态、I/O等待占用的CPU时间。wa高就是典型的I/O瓶颈。
6.2 做一次能看出差异的对比实验
先用一个小文件测试,数据量小于内存:
# 生成一个 1GB 的测试文件 dd if=/dev/zero of=/tmp/testfile bs=1M count=1024 # 第一次读,数据要从磁盘进内存 time cat /tmp/testfile > /dev/null # 第二次读,数据可能还在页缓存里 time cat /tmp/testfile > /dev/null两次耗时会有明显差异,第二次通常快好几倍,这个差距就是页缓存的作用。如果想强制清空缓存再对比,可以用:
sync echo 3 > /proc/sys/vm/drop_caches第二条命令需要root权限,而且只在测试环境用,生产环境执行会瞬间打满磁盘I/O。
6.3 观测时容易忽略的几个细节
第一,free命令里的available才是真正可用的内存,free那一列低不代表内存紧张,因为Linux会把空闲内存拿去做缓存。第二,iostat里的%util接近100%只说明设备忙碌,不等于性能瓶颈,还要看await(平均等待时间)。第三,vmstat的wa并不包含所有I/O等待,某些异步I/O不会计入,所以要结合iostat一起看。
7. 踩过坑之后总结的几条经验
关于内存分配器,很多人不知道glibc默认的分配器在多线程场景下会有锁竞争,表现是多线程程序线程数上去后性能反而下降。换成jemalloc或tcmalloc往往能缓解,这不是玄学,是分配器实现差异导致的真实差距。
关于容器环境,free看到的内存和容器实际能用的内存不是一回事。容器里跑的应用要读 cgroup 的限额文件才知道自己的真实上限,Java应用要特别注意,JVM默认会按宿主机内存来算堆大小,容器限额不显式指定就容易OOM被杀。
关于磁盘管理,扩容和合并分区这类操作前一定要备份。热词里"磁盘1合并之后里面的东西不见了""vm指定文件不是虚拟磁盘"这两个,本质都是操作前没确认对象和后果。存储操作不可逆的比例远高于内存和CPU类的操作,谨慎一点没坏处。
我个人在实际操作中的体会是,这套体系里最值得花时间理解的就是"速度差"这一个概念。CPU快、内存中、磁盘慢,所有的缓存、预取、异步、批量,都是为了抹平这个差距。你把这个差距的数量级记牢,遇到性能问题时先问自己"这次访问落在哪一层",方向基本上不会错。剩下的事情,就是查工具、看数据、验证猜想,一步步把范围缩小。