(书稿底稿)(C/C++)第一章:CPU基础知识 1.2.5各种内存介绍

来源:互联网 发布:python入门课程推荐 编辑:程序博客网 时间:2024/05/16 07:11


1.2.5各种内存介绍

CORE I7  32K的指令、数据L1256K L2

前面一直提到一级缓存L1、二级缓存L2,这小节我们仔细讨论CPU和操作系统使用的各种内存,这些内存直接关系到我们程序的执行速度,这些内存使用机制由操作系统和CPU负是不会为某个程序进行妥协修改的,因此了解这些机制后,平时编码时就知道如何实现高效访问,也可以根据此机制分析并优化程序行为。所谓物竞天择,我们人类正是因为适应并充分利用了大自然环境,才得以让种族高速发展。CPU和操作系统对内存的控制机制是大自然的话,我们人类就是在大自然中执行的一个程序而已,人类一直在追求“程序变身变系统”,但目前来看还不知道哪年月可以翻身做自然界主人。

计算机中内存的类型依据速度和成本,从高到低依次为:寄存器、高速缓存(L1 L2 L3)、主存储器、 page file,他们的性能和在计算机中的作用相差很大。

主存储器:通常采用半导体存储器,有两种主要类型:随即存取存储器RAM和只读存储器ROMRA可以按CPU命令进行读写操作,而ROM事先加载了固化的数据和软件,CPU只能读,典型的例如PC中负责将操作系统从硬盘加载到内存的的BIOS专用程序,存储在ROM中的软件经常称为固件Firmware,目前最常见的是Android手机刷系统常常也叫刷固件,因为Android系统是存储在ROM中的。断电后RAM数据丢失,ROM则会保留数据;

寄存器:这是在CPU内部的一组特殊的内存单元,读写速度非常快,不同CPU2-100多个寄存器,他们被用在访问最频繁的数据项上,计算机指令、数据的读取执行绝大多数都要依赖寄存器。

高速缓存:在大多数比较复杂的计算机中,位于CPU和主存储器中间,都存在一个或多个比寄存器慢但比主存储器速度快的规模较小的高速RAM缓冲存储器简称高速缓存—CACHE,他们的出现很好的解决了CPU和主存储器之间的速度匹配问题一般由一级L1、二级L2和三级L3缓存组成,一般高速缓存中存储的都是被频繁调用到的数据,这些并不需要程序员的任何操作,由硬件完成;

从磁盘文件的page file一直到寄存器,这一系列内存分列称为层级缓存结构(cache hierarchy),越靠近CPU速度越快,成本越高、容量越低,反向刚好相反。高层级缓存中的数据是从低层级缓存中的数据一部分映射来的。下图红色区域是CPU正在执行的代码和数据,绿色是被执行程序未被执行到的部分。数据在不同的层级缓存中上升和下降依赖于预先制定的一系列标准和条件。

1.2.5.1      内存访问命令的生命周期

CPU读取和写入内存数据的步骤简化的说就是:

读取数据:调用内存装载指令来将内存中的数据装载到CPU中,一般是寄存器

写入数据:调用存储指令将数据保存到对应的内存中

        从上面看其逻辑并不复杂,但真实情况中并非如此简单的直接调用相关指令进行读取和写入,还会涉及到硬件的缓存(一级缓存L1、二级缓存L2)。和数据库打过交道的人知道,数据库也使用缓存临时存储各种被频繁使用的数据,用以提升数据库的性能,比如某条SQL语句查询了一条信息后,这条信息就被缓存到数据库缓存中,如果再次查询本条数据,就直接从缓存返回数据,不再需重新对数据库进行访问,缓存的访问速度要远高于数据库中提取数据的速度,且降低了数据库的访问并发数,从而达到提升效率目的。CPU对应的缓存机制也是类似效果。

        缓存效果下,CPU读取数据时首先会在一级缓存L1中寻找,若存在,直接将其值返回,如果不存在就需要去二级缓存中寻找,同理二级缓存找不到,就去主内存中寻找,因为从访问二级缓存开始,不但访问时间明显变大而且可能需要重新对数据进行缓存,从而影响到执行的效率。

        CPU能够快速的执行代码和处理数据,缓存是个关键角色,如果我们不了解层级结构的缓存结构和功能,就很难理解系统中针对缓存而设计的功能。

基本知识

        CPU和内存系统呈现消费-生产关系,CPU消费内存和硬盘中的内容。因为技术和设计的原因,CPU执行速度远远超过内存的速度,也就是CPU时钟周期大于内存和总线的时钟周期,因此造成CPU等待内存的情况,CPU速度越快冲突越大

        1972年英属哥伦比亚大学计算机中心的Jeff Berryman在杂志上发表了一篇很著名、很有意思的故事“物品管理员”(The Thing King),这个故事只讲述了要遵守的规则,其他需要自己领悟,这个故事可以帮助理解缓存和虚拟内存的机制和原理。故事内容如下:

        这个故事类似现在的物流公司给商品打标记

        规则:

  1. 每个员工都要负责上百万的物品

  2. 所有物品都被保存在物品箱中,每个物品箱存储着4096个货物(相信我们这些编程人员对这个数字很敏感,到处都可能碰到4096,所以他也是我们的老朋友了),在同一个物品箱保存的货物叫同箱货物

  3. 物品箱不是被放在办公室就是放在仓库,办公室的租金很贵,吝啬的老板租用的办公室比较小,根本不可能放得下所有的物品箱

  4. 办公室就一个,仓库因为租金很便宜,因此有很多个,员工都在办公室工作

  5. 每个货物都有自己的标示     

  6. 员工要做的事就是给货物打标记,可以给同一个货物打多次标记

  7. 员工只能给属于自己的货物打标记,不准标记别人的,他们不要雷锋

  8. 鉴于他们不雇佣超人员工,因此只有在办公室的货物才能打标记

  9. 只有物品管理员知道某件货物是在办公室还是在仓库

  10. 在办公室的物品如果不处理,就会变脏,时间越长越脏

  11. 员工领取货物的方式只能是去找物品管理员,放心,万能的物品管理员会非常小心不会出错

  12. 如果你要打标记的货物刚好在办公室,你就可以很快拿到货物并打上标记。如果货物是在仓库,物品管理员要先将存放着这个货物的箱子从仓库提到办公室。如果办公室没有位置了,他会找最脏的箱子将这个箱子里存放的货物全部放回箱子中(之前有人从箱子里提取货物打标记),不管是谁使用了这个箱子中的货物统统收走,然后将箱子放到仓库,空出来的位置放置最新的物品箱,提取货物必须以整箱为单位从仓库提取。然后物品管理员会将你需要的货物给你继续打标记,你并不需要知道货物具体是在办公室还是在仓库。

  13. 员工只要给负责的货物打标机就可以了,物品管理员知道哪个员工负责哪些货物,保证员工不会错标到别人的货物上。

提示:

  1. 一般来说,物品管理员手里会有一张庞大的表,记录了每个人负责的货物

  2. 员工在打标记的时候,并不需要知道办公室有几个员工在同时工作

  3. 物品管理员的工作比较少,基本就是在办公室和仓库之间来回搬送物品箱

  4. 根据上面的规则,如果想高效的工作,就要将存放最频繁使用的货物的物品箱尽量一直放在办公室里,将不怎么使用的货物放到仓库

无敌的物品管理员!!

 

1.2.5.2      一级缓存L1

        寄存器并不能算是内存,缓存技术并未怎么涉及到寄存器,因此我们就不讲寄存器了,有兴趣的朋友可以自行查资料。

CPU可以访问的内存中,一级缓存因为它的材质和距CPU核心距离最近的缘故,它的访问速度最快,因此一级缓存中存放的是CPU马上要执行的代码和数据,有专门的选择预测算法来预测CPU将要执行哪些代码。高速访问是需要代价的,一级缓存是Static RAM(SRAM),它的每个内存单元都是由4-6个晶体管组成(DRAM只需要1个),因此成本高昂。

现代的CPU,一级缓存和CPU集成到一个硅元素上,和CPU内核的距离非常近,因此访问速度非常快。但也是有缺点的:主内存(通常就是我们机器中的内存条)与CPU的距离和与一级缓存的距离一样远,当CPU执行的指令在一级缓存上不存在的时,就需要从主内存提取,提取的时候CPU需要等待,因此很浪费时间,CPU越快这种情况就越频繁越糟糕,主内存和一级缓存的时间差异见前图。

一级缓存和主内存访问时间差有近50ns,按照1GHzCPU来计算,一次等待需要浪费50个时钟周期,对于采用了管道技术的CPU来讲这个是个很大的浪费。

1.2.5.3二级缓存L2

        在上面一级缓存章节中提到了当数据不在一级缓存时的时间浪费现象,解决这个问题的策略是在一级缓存和主内存之间再增加一个更大的缓存区就是二级缓存L2,一级缓存因为成本造价昂贵不适合扩充很大。通常一级缓存中的数据时二级缓存中的数据的一个子集,也可以说成一级缓存是二级缓存的一个子集。

 

 

 

1.2.5.4Cache Misses

        CPU需要访问中某个数据时,去一级缓存提取,发现一级缓存不存在这个数据,这种情况就叫Cache Misses,对应我们上面讲述的“物品管理员”例子就是,员工去找管理员要某件货物,刚好这个货物不在办公室而是在仓库,这种情况下管理员需要去仓库提货,就需要更长时间。因此发生Cache MissesCPU执行速度就会降低,数据库缓存中发生此情况时,就需要去数据库中查询数据,肯定也会降低执行效率。

        引发CacheMisses原因大概有如下情况:

  1. 必然性misscompulsory miss):程序刚刚启动,初次被CPU执行时,CPU需要的数据肯定不在缓存中,没有任何机制可以预测将要执行哪个程序,这种情况肯定无法避免因此才称为必然性MISS

  2. CPU请求的数据本来保存在缓存中,但是因为一些策略、机制原因,被从缓存中删除了。删除的原因基本因为空间或时间的缘故。缓存的容量有限,如果需要存放新数据但缓存又无剩余空间时,就会选择一些比较老或一段时间未被使用的数据删除掉,替换成新数据。

如果想分析具体发生Miss的原因,要了解缓存的数据选取机制。

 

1.2.5.5缓存数据选取机制

缓存数据选取机制对于编程来说很重要。利用其特性可以提高程序的性能和效率。

        缓存数据选取机制,利用空间和时间两个重要属性来提取数据

        空间:若CPU需要某个内存中存储的数据时,紧随这个数据的其他数据被CPU使用的几率很大,因为一般有关联的数据通常放在一起。比如,吃饭的时候,父母让你去厨房拿碗,你肯定会顺便将筷子也拿过来,因为碗筷有一定联系,只要吃的不是手抓饭,这些都是吃饭必须的,且都放在橱柜中,空间上也是在一起的。

        编码中,空间属性对数据和代码的影响各有特点,我们分开说

        数据的空间属性:

                  这种类型是最典型且容易理解的,如播放MP3DVD以及大数据量数据集的文件访问。

                  因此,根据此属性得知:相互联系密切的数据应尽可能的在连续的内存中存放,这样CPU可以将这些相关联的数据一同读入缓存放在一起在同一批次中高效实用。

        代码的空间属性:

                  和数据大致相同,一个好的代码应尽可能的避免goto跳转语句和跨度比较大的分支语句,这样CPU可以在连续的代码库中持续执行。一般处理软件、游戏、虚拟工具对此要求高,因为这些软件经常重复执行少量代码操作大容量数据。比如游戏魔兽世界下副本,就是来回的使用经常使用的那些技能,每个技能对应少量的代码,每使用一次技能就是调用一次对应的代码,也就是频繁的重复执行某些少量代码,但是需要处理大量数据,曾经有人用圣骑士下副本只需要将几个技能放满全部快捷键然后“用脸滚键盘”就可以打通副本。加入暴雪的技术人员将这几个技能代码的够远,以至于CPU无法将这些技能代码一同缓存起来,重复按这些技能时,CPU就会不停的反复的缓存这些代码,势必很影响游戏的反应速度,然后玩家用脸滚着滚着就发现游戏反应不过来了,所以看来能让玩家顺畅的“用脸滚键盘”还是需要技术人员支撑的。

        还一个最常见的范例,就是微软的WORD办公软件,WORD功能很大强,但是只有少量功能最常用。很多人经常碰到这样的问题,将一段文字修改格式后,比如加粗,点击菜单栏的保存按钮,有时候WORD软件会发生短信的无响应,等保存完毕,再重复点击“保存”按钮时,速度就会非常快。我们分析下原因:在代码中,“加粗功能”的代码和“保存功能”功能的代码位置间隔比较远,以至于在缓存并执行“加粗功能”的代码时,无法缓存到“保存功能”,当执行“保存功能”时,CPU会从“加粗功能”代码区域跳转到“保存功能”代码区域,并刷新一级和二级缓存,因此执行速度比较慢。因此此类软件的性能对CPU的频率的敏感性并不大,对缓存大小很敏感,足够大的缓存区域才能将常用的功能代码都缓存起来,提升执行效率,如果缓存小,则CPU需要不断切换代码区域让缓存不停的刷新。这就是为什么OFFICE系列软件在老CPU上特别是赛扬Celeron上执行速度比现在CPU速度要慢很多,重要差别并非因为CPU频率的差异,即便是赛扬CPU,运行OFFICE时,CPU使用率也很低的,重要差别是最新CPU的缓存大。因此我们在编写类似软件的时候,应该考虑将相互关联功能(使用完这个功能很可能继续使用的其他功能)在代码中放到一起,以便让CPU一次性放到缓存中。

        Inline函数性能好的一个缘故也是因为缓存,编译阶段将inline函数代码直接扩展到调用函数中,CPU执行代码时,就将这2个函数代码一同缓存到了缓存中,其他加速原因后续章节会介绍。

        时间属性对代码和数据的影响:

                  这个强调时间上的反复执行。

                  Photoshop软件的一个滤镜功能:将普通的照片转换为底片模式。转换功能的算法代码逐个读取所有像素点然后反制执行。

                  在媒体播放、MP3播放时,文件中的数据被播放完毕就无用了,不会被反复使用,很少人会不断回退的看一段视频,因此将这些数据存入缓存就是一种浪费,因为他们就被使用一次,如果将这些不会被反复使用的数据放入缓存中在术语上称为缓存污染(pollute the cache)。在媒体播放和游戏中很容易出现此种情况。比如冷却时间为30分钟的技能,对于这样的代码缓存对他们的意义不大。使用过amazon kindle电子书的朋友在看书的时候按“上一页”“下一页”感觉很比较大,“下一页”速度是“上一页”的很多倍,明显是对下一页进行了缓存,对上一页没有缓存,因为看书的人很少会翻回去,因此“上一页”amazon就视其为只使用一次的内容。

                  从上面介绍大家是不是觉得时间的属性和空间的属性非常相似?不要怀疑我不小心将空间属性说明拷贝了一份,因为时间的属性暗含了空间属性,当然反之不行。

        

总结:被反复使用的且相互有关联的数据,有关联并不代表着会被反复使用,内存中映射的文本文件内容是个数据被反复使用的例子,但是MP3歌曲文件并不是个被反复使用的例子。

        可以看到,就算在同一个程序中缓存对数据和代码的影响不同,这也促使编译器编译程序时进行分段处理,将代码和数据分段存放,如媒体的代码部分,其表现出典型的时间重复性质,播放的时间内不断的执行相同的代码进行播放,而MP3的数据却很差,甚至会污染缓存,这种典型情况启发了设计人员,他们将缓存分为代码缓存和数据缓存,代码缓存也叫I-Cache,数据缓存叫D-Cache。划分后具体提高的性能数据依赖于缓存大小、操作系统等因素。

        具体缓存如何执行就很复杂了,如果完全讲述清楚需要花费大量文章,其具体机制不在本书关心范围,本书只介绍影响到编码的概念和理解相关部分,有兴趣的朋友请自行查找相关资料。

0 0
原创粉丝点击