当前位置:首页 > 技术学院 > 技术前线
[导读]很多开发者写代码时调用内存读写操作,总以为CPU会直接跨过所有中间环节,瞬间完成和物理内存的数据交互。但实际上CPU和内存之间的速度差距已经超过上百倍,现代CPU根本不可能直接等待慢悠悠的内存响应。从你写出的一行赋值语句被编译成机器指令,到CPU最终把数据从内存里完整读出来,中间要经过缓存层级、内存控制器、总线传输的一整套复杂链路,每一个环节都有专门的硬件机制来弥合CPU和内存之间巨大的速度鸿沟。彻底理清CPU读写内存的完整流程,你就能写出对内存极其友好的高性能代码。

很多开发者写代码时调用内存读写操作,总以为CPU会直接跨过所有中间环节,瞬间完成和物理内存的数据交互。但实际上CPU和内存之间的速度差距已经超过上百倍,现代CPU根本不可能直接等待慢悠悠的内存响应。从你写出的一行赋值语句被编译成机器指令,到CPU最终把数据从内存里完整读出来,中间要经过缓存层级、内存控制器、总线传输的一整套复杂链路,每一个环节都有专门的硬件机制来弥合CPU和内存之间巨大的速度鸿沟。彻底理清CPU读写内存的完整流程,你就能写出对内存极其友好的高性能代码。

一、从应用代码到访存指令:读写请求的起源

CPU发起内存读写的第一步,根本不是直接和内存打交道,而是从你写的高级语言代码开始的。你在代码里定义的变量赋值、结构体字段修改这些操作,经过编译器编译之后,会被翻译成对应的机器指令。在精简指令集架构里,专门有独立的加载和存储指令负责内存读写,所有运算操作都只能在寄存器里完成,想要操作内存里的数据,必须先通过加载指令把数据从内存搬到寄存器,运算完成之后再通过存储指令把结果写回内存。在x86这类复杂指令集架构里,很多运算指令可以直接附带内存操作,但是底层执行的时候依然会自动拆解成访存操作,先把数据从内存读入寄存器再完成运算。

CPU的指令流水线在执行这条访存指令的时候,首先会从指令里解析出要访问的虚拟内存地址。现代操作系统全部运行在保护模式下,应用进程使用的所有地址都是虚拟地址,不是直接指向物理内存的真实地址,CPU不能直接用这个虚拟地址去访问物理内存,必须先经过地址翻译的步骤,把虚拟地址转换成对应的物理内存地址。

CPU里的内存管理单元首先会去查找页表缓存,也就是快表,里面缓存了最近常用的虚拟地址到物理地址的映射关系。如果要访问的虚拟地址对应的映射已经在快表里缓存,就能立刻得到对应的物理地址,不需要走漫长的页表遍历流程,整个地址翻译过程只需要一两个时钟周期就能完成。如果快表里没有对应的映射条目,就会触发快表不命中,CPU需要遍历内存里的页表,找到对应的映射关系,同时把这个新的映射条目加载进快表,这个过程的延迟会高很多。

二、缓存层级的命中判断:避免直接访问内存的核心机制

得到物理地址之后,CPU根本不会直接拿着这个地址去访问物理内存,它首先会沿着CPU内部的多级缓存层级,从L1缓存开始逐层查找,看看要访问的数据是不是已经提前缓存在CPU内部了。

现代CPU的缓存体系是严格分层的,L1缓存的速度最快,容量也最小,每个CPU核心都有自己独立的L1指令缓存和数据缓存,访问延迟只有几个时钟周期。如果要读写的数据刚好在L1缓存里命中,CPU根本不需要和外部的任何硬件交互,直接就能在几个时钟周期里完成数据的读写操作,整个过程的速度和CPU的运算速度完全匹配。

如果L1缓存没有命中,CPU会继续去访问延迟稍高、容量更大的L2缓存,L2缓存也是每个核心独立的。如果L2缓存也没有找到目标数据,就会继续访问所有核心共享的L3缓存。如果数据在L3缓存里命中,虽然延迟比L1高不少,但是依然比直接访问物理内存快好几倍。

CPU的缓存和内存之间的数据传输最小粒度是64字节的缓存行,哪怕你只是想读取一个4字节的整型数据,CPU也会把包含这个数据的整个64字节连续缓存行全部从下一层级加载到当前缓存里。这个设计的依据就是程序的局部性原理:如果程序刚刚访问了某个地址的数据,那么接下来很大概率会再次访问这个数据,或者访问和它相邻的其他数据,一次性把整个缓存行加载进来,后续的访问大概率都会直接命中缓存,大幅降低平均访存延迟。

三、内存控制器与总线传输:最终完成和物理内存的数据交互

如果三级缓存全部都没有命中目标数据,CPU才会真正发起对物理内存的访问请求。这个访存请求会被发送到CPU内部的内存控制器,内存控制器会对收到的大量访存请求做重排优化,把原本随机分布的读写请求按照内存芯片的行地址排序,尽可能把相邻地址的请求合并在一起,大幅提升内存总线的传输效率。

之后内存控制器通过主板上的内存总线,把物理地址和读写控制信号发送给内存模组。内存模组收到请求之后,经过一系列的行激活、列选通的时序操作,把对应地址里的数据读取出来,通过内存总线传输回CPU的缓存层级。整个从内存读取数据的过程,需要上百个CPU时钟周期才能完成,这个时间足够CPU执行上百条普通运算指令。为了掩盖这么长的访存延迟,CPU的指令流水线会切换去执行其他不相关的指令,不会让整个流水线空转等待内存返回数据,这就是现代CPU的乱序执行机制。

写内存的流程和读内存类似,CPU不会直接把数据同步写回物理内存,而是先把数据写入缓存里对应的缓存行,同时把这个缓存行标记为脏。之后CPU会根据缓存的替换策略,在合适的时机才把脏缓存行的内容异步写回物理内存。这种设计可以避免每一次写操作都直接触发漫长的内存访问,大幅提升写操作的性能。

整个CPU读写内存的完整链路,本质上是一套层层递进的缓冲体系,用容量小但速度极快的缓存尽可能挡住绝大多数内存访问请求,只有极少数的缓存不命中请求才会最终落到物理内存上。理解这套链路的运行逻辑,你就能通过调整数据结构的内存布局,最大化缓存命中率,写出性能远超普通代码的极致高效程序。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭