当前位置: 首页 > Linux

内存随机访问也比顺序访问慢,这会让你对内存IO过程有一个深刻的理解

时间:2023-04-06 20:06:01 Linux

通常大家都知道内存访问是很快的。今天,让我们把探究精神发挥到极致,思考两个问题。问题一:内存访问有什么延迟?你做粗略的估计吗?比如笔者的内存条Speed显示为1066MHz,那么是否可以算出内存IO延迟为1s/1066MHz=0.93ns?这个算法是非常错误的。问题2:是否存在内存中随机IO比顺序IO慢的问题?我们都知道磁盘的随机IO比顺序IO慢很多(操作系统底层也实现了电梯调度算法来缓解这个问题),那么内存的随机IO会不会比顺序IO慢IO?要想彻底搞懂以上两个问题,我想还得从内存IO的物理过程中寻找答案。在我进入有关记忆如何工作的无聊内容之前,让我告诉您一个关于图书管理员的故事。我想给你讲个故事,给你介绍一个男人,图书管理员。在我们的这个故事里,你就是故事的主角。你有房子,家里有个佣人,每天帮你处理各种账本资料。但是北京的房价太贵了,所以你的房子很小,只能放64本书。你家马路对面就是北京图书馆(你家虽小但位置很好),在那里你可以找到你需要的所有书籍。有图书管理员负责帮你找到你想要的书。嗯,接下来,导演打招呼,开场了!场景一:你发现你需要编号为0的书的计算结果,你的仆人过马路告诉图书管理员,让他帮我把0-63的书拿出来。图书管理员会帮你在电脑前查看,书在二楼。于是他费了点功夫,乘电梯到了二楼。到了二楼,他花了点时间帮你找。然后你的仆人端着64本书放在客厅,拿起第0本书帮你处理。情况二:你发现需要1号书的计算结果,告诉你的仆人。您的仆人可以直接从起居室处理,这次您的等待时间最短。场景3:您发现您需要第65号书,并告诉您的仆人。您的仆人再次穿过马路去找图书管理员。图书管理员还在二楼。听说这次需要65-127。这一次他不用再花时间找楼层了。只是花时间找到这本书。你的仆人把65-127号的书放在客厅(扔掉之前的0-63),帮你开始处理65号的书。场景4:你发现你需要10000号的书,你告诉你的仆人。您的仆人穿过马路去图书馆,找到看门人。这次管理员发现你要的书在10楼,坐电梯到那里要花点时间。他走后,他将不得不花一些时间来帮助您找到它。在这四种场景中,我想你一定发现了不同情况下的耗时差异。场景1和场景4花费的时间最多。因为图书管理员需要花时间坐电梯找楼层,需要花时间在楼里找书。场景3次之,因为图书管理员直接在楼上,你只需要花时间在楼里找书。场景2最快,因为你只需要佣人帮你从客厅拿,甚至不用过马路。.之所以编出这样的例子,是因为内存的工作方式与其太相似了。接下来我们进入内存的实际分析。内存的物理结构在《带你理解内存对齐最底层原理!》中,我们了解了内存颗粒的物理结构和IO过程。今天再回顾一下。内存是由芯片组成的。每个芯片内部由8个bank组成。它的结构如下图所示:而每一个bank就是一个二维平面上的矩阵,我们在上一篇文章中提到过。矩阵中的每个元素存储1个字节,即8位。每当CPU向内存请求数据时,内存芯片总是以8个bank并行工作。每个bank定位到行地址后,将相应的行复制到行缓冲区中。根据列地址,取出对应元素中的数据,8个bank拼接数据,可以返回一个64位宽的数据给CPU。根据以上图片,我们可以对内存IO过程有个大概的了解。在这个过程中,每一步操作之间都有一定的延迟。让我们继续了解这些延迟。内存IO延迟在《从DDR发展到DDR4,内存核心频率指标其实基本上就没太大的进步。》的最后,大家应该还记得我们提到过内存有四个参数:CL-tRCD-tRP-tRAS。今天来详细了解一下这四个参数的含义:CL(ColumnAddressLatency):发送一个列地址到内存到数据开始响应的周期数tRCD(RowAddresstoColumnAddressDelay):开启一行内存和访问列所需的最小时钟周期数tRP(RowPrechargeTime):从发出预充电命令到打开下一行所需的最小时钟周期数。tRAS(RowActiveTime):行激活命令和预充电命令发出之间所需的最小时钟周期数。即限制下一次预充电时间。需要注意的是,除CL为固定循环数外,其余三个均为最小循环数。另外,以上参数均以时钟周期为单位。因为现代内存在一个时钟周期的上下沿传输一次数据,所以可以使用Speed/2来获取。比如笔者机器的Speed是1066MHz,时钟周期是533MHz。可以通过dmidecode命令查看自己的机器:#dmidecode|grep-P-A16"MemoryDevice"MemoryDevice......Speed:1067MHz......类似于"librarian",memorychip也有类似的工作场景:场景一:你的进程需要一个byte内存地址0x0000处的数据。此时CPU向内存控制器发送请求,内存控制器预充电行地址,需要等待tRP时钟周期。.然后发出打开一行内存的命令,并等待tRCD时钟周期。然后发送列地址,然后等待CL周期。最后将0x0000-0x0007的数据全部返回给CPU。CPU把这些数据放到自己的缓存中,帮你开始对0x0000的数据进行操作。场景二:你的进程需要内存地址0x0003的一个字节数据,CPU发现它存在于自己的缓存中,直接使用即可。在这种情况下,实际上根本没有内存IO发生。场景3:您的进程需要内存地址0x0008处的一个字节数据,但CPU的缓存未命中,因此它从内存控制器请求它。内存控制器发现行地址与上次工作的行地址一致。这次发送列地址后只需要等待CL周期,就可以拿到0x0008-0x0015的数据返回给CPU。场景4:您的进程需要内存地址0xf000处的一个字节数据,而CPU的缓存未命中,因此它向内存控制器请求它。看了内存控制器(心里有点郁闷),这次行地址又变了,没错,和场景1一样,继续等待tRP+tRCD+CL周期后,数据就可以了被取出并返回。实际的计算机内存IO过程还需要将逻辑地址和物理地址进行转换,这里忽略。结论场景一和场景四是随机IO,场景二没有内存IO,场景三是顺序IO。通过上面的过程描述我们可以得出结论。和磁盘一样,内存也存在随机IO比顺序IO慢的问题。如果行地址与上次访问不一致,则需要重新复制行缓冲区,延迟时间需要为tRP+tRCD+CL。而如果是顺序IO(行地址不变),只需要CL周期就可以完成。然后我们估计内存的延迟。笔者机器上的内存参数Speed为1066MHz(通过dmidecode查看)。这个值除以2就是时钟周期的频率=1066/2=533Mhz。它的延迟周期是7-7-7-24。这种情况下,随机IO需要tRP+tRCD+CL时钟周期,7+7+7=21个周期。但是还有一个tRAS的限制,两个rowaddressprecharge不能小于24。所以我们要按24来计算,24*(1s/533Mhz)=45nssequentialIO这种情况下,只需要CL时钟周期7\*(1s/533Mhz)=13nsExpansion:查看CPU的CacheLine,因为对于内存来说,随机IO比顺序IO有数倍的开销。所以,操作系统在工作的时候,会尽量让内存通过顺序IO来处理。练习的关键是CacheLine。当CPU发现缓存未命中时,它实际上不会从内存中请求1个字节或8个字节。而是一次取64个字节,然后放到自己的Cache中存储。用上面的例子,如果随机请求8个字节:耗时45ns如果随机请求64个字节:耗时45+7\*13=136ns开销并不大,因为只有第一个字节是RandomIO,后面7个字节是sequentialIO。数据是8次,但是IO时间只有3次,取出来的数据后面很有可能用到,所以这是在电脑内部做的,这样可以帮你避免一些随机IO!此外,内存还支持突发(bursttransfer)模式。这种模式下,行地址和列地址只能传入一次,命令内存返回内存开头的连续字节数据,比如64字节。这种模式下,只有前8个字节需要真正的行列访问延迟,后面的7个字节可以根据内存的数据频率直接吐出,耗时更少。练内功:内存部分:1.带你了解内存对齐的底层原理2.内存随机访问比顺序访问慢,让你深刻理解内存IO流程3.从DDR到DDR4,内存核心频率基本一致没太大进步4.实测内存有顺序IO和随机IO的访问延迟差异5.揭穿内存厂商的“谎言”,实测内存带宽的实际表现6.NUMA架构下内存访问延迟的区别!7.PHP7内存性能优化精髓8.一个内存性能提升的项目实践9.挑战Redis单实例内存最大极限,“遭遇”NUMA陷阱!我的是《练内功》。在这里我不是简单地介绍技术理论,也不是只是介绍实践经验。而是理论联系实际,用实践加深对理论的理解,用理论提高技术实践能力。欢迎关注我的,分享给你的朋友吧~~~