首页 > 其他分享 >37 | 高速缓存(上):“4毫秒”究竟值多少钱?

37 | 高速缓存(上):“4毫秒”究竟值多少钱?

时间:2023-05-22 15:33:35浏览次数:38  
标签:缓存 访问 Cache 37 毫秒 内存 数据 CPU 高速缓存


在这一节内容开始之前,我们先来看一个 3 行的小程序。你可以猜一猜,这个程序里的循环 1 和循环 2,运行所花费的时间会差多少?你可以先思考几分钟,然后再看我下面的解释。





int
           [] arr = 
           new
            
           int
           [
           64
            * 
           1024
            * 
           1024
          
          
          
// 循环1
          
for
            (
           int
            i = 
           0
           ; i < arr.length; i++) arr[i] *= 
           3
          
          
          
// 循环2
          
for
            (
           int
            i = 
           0
           ; i < arr.length; i += 
           16
           ) arr[i] *= 
           3





在这段 Java 程序中,我们首先构造了一个 64×1024×1024 大小的整型数组。在循环 1 里,我们遍历整个数组,将数组中每一项的值变成了原来的 3 倍;在循环 2 里,我们每隔 16 个索引访问一个数组元素,将这一项的值变成了原来的 3 倍。


按道理来说,循环 2 只访问循环 1 中 1/16 的数组元素,只进行了循环 1 中 1/16 的乘法计算,那循环 2 花费的时间应该是循环 1 的 1/16 左右。但是实际上,循环 1 在我的电脑上运行需要 50 毫秒,循环 2 只需要 46 毫秒。这两个循环花费时间之差在 15% 之内。


为什么会有这 15% 的差异呢?这和我们今天要讲的 CPU Cache 有关。之前我们看到了内存和硬盘之间存在的巨大性能差异。在 CPU 眼里,内存也慢得不行。于是,聪明的工程师们就在 CPU 里面嵌入了 CPU Cache(高速缓存),来解决这一问题。


我们为什么需要高速缓存?


摩尔定律


如果拿我们现实生活来打个比方的话,CPU 的速度好比风驰电掣的高铁,每小时 350 公里,然而,它却只能等着旁边腿脚不太灵便的老太太,也就是内存,以每小时 3 公里的速度缓慢步行。因为 CPU 需要执行的指令、需要访问的数据,都在这个速度不到自己 1% 的内存里。



37 | 高速缓存(上):“4毫秒”究竟值多少钱?_数据


随着时间变迁,CPU 和内存之间的性能差距越来越大


为了弥补两者之间的性能差异,我们能真实地把 CPU 的性能提升用起来,而不是让它在那儿空转,我们在现代 CPU 中引入了高速缓存。


从 CPU Cache 被加入到现有的 CPU 里开始,内存中的指令、数据,会被加载到 L1-L3 Cache 中,而不是直接由 CPU 访问内存去拿。在 95% 的情况下,CPU 都只需要访问 L1-L3 Cache,从里面读取指令和数据,而无需访问内存。要注意的是,这里我们说的 CPU Cache 或者 L1/L3 Cache,不是一个单纯的、概念上的缓存(比如之前我们说的拿内存作为硬盘的缓存),而是指特定的由 SRAM 组成的物理芯片。


这里是一张 Intel CPU 的放大照片。这里面大片的长方形芯片,就是这个 CPU 使用的 20MB 的 L3 Cache。



37 | 高速缓存(上):“4毫秒”究竟值多少钱?_数据_02


现代 CPU 中大量的空间已经被 SRAM 占据,图中用红色框出的部分就是 CPU 的 L3 Cache 芯片


运行程序的时间主要花在了将对应的数据从内存中读取出来,加载到 CPU Cache 里。CPU 从内存中读取数据到 CPU Cache 的过程中,是一小块一小块来读取数据的,而不是按照单个数组元素来读取数据的。这样一小块一小块的数据,在 CPU Cache 里面,我们把它叫作 Cache Line(缓存块)。


16 个整型数正好是 64 个字节。于是,循环 1 和循环 2,需要把同样数量的 Cache Line 数据从内存中读取到 CPU Cache 中,最终两个程序花费的时间就差别不大了。


知道了为什么需要 CPU Cache,接下来,我们就来看一看,CPU 究竟是如何访问 CPU Cache 的,以及 CPU Cache 是如何组织数据,使得 CPU 可以找到自己想要访问的数据的。因为 Cache 作为“缓存”的意思,在很多别的存储设备里面都会用到。为了避免你混淆,在表示抽象的“缓存“概念时,用中文的“缓存”;如果是 CPU Cache,我会用“高速缓存“或者英文的“Cache”,来表示。


Cache 的数据结构和读取过程是什么样的?


现代 CPU 进行数据读取的时候,无论数据是否已经存储在 Cache 中,CPU 始终会首先访问 Cache。只有当 CPU 在 Cache 中找不到数据的时候,才会去访问内存,并将读取到的数据写入 Cache 之中。当时间局部性原理起作用后,这个最近刚刚被访问的数据,会很快再次被访问。而 Cache 的访问速度远远快于内存,这样,CPU 花在等待内存访问上的时间就大大变短了。



37 | 高速缓存(上):“4毫秒”究竟值多少钱?_缓存_03


这样的访问机制,和我们自己在开发应用系统的时候,“使用内存作为硬盘的缓存”的逻辑是一样的。在各类基准测试(Benchmark)和实际应用场景中,CPU Cache 的命中率通常能达到 95% 以上。


直接映射 Cache


CPU 访问内存数据,是一小块一小块数据来读取的。对于读取内存中的数据,我们首先拿到的是数据所在的 内存块 (Block)的地址。而直接映射 Cache 采用的策略,就是确保任何一个内存块的地址,始终映射到一个固定的 CPU Cache 地址(Cache Line)。而这个映射关系,通常用 mod 运算(求余运算)来实现。下面我举个例子帮你理解一下。


比如说,我们的主内存被分成 0~31 号这样 32 个块。我们一共有 8 个缓存块。用户想要访问第 21 号内存块。如果 21 号内存块内容在缓存块中的话,它一定在 5 号缓存块(21 mod 8 = 5)中。



37 | 高速缓存(上):“4毫秒”究竟值多少钱?_计算机原理_04


Cache 采用 mod 的方式,把内存块映射到对应的 CPU Cache 中


实际计算中,有一个小小的技巧,通常我们会把缓存块的数量设置成 2 的 N 次方。这样在计算取模的时候,可以直接取地址的低 N 位,也就是二进制里面的后几位。比如这里的 8 个缓存块,就是 2 的 3 次方。那么,在对 21 取模的时候,可以对 21 的 2 进制表示 10101 取地址的低三位,也就是 101,对应的 5,就是对应的缓存块地址。



37 | 高速缓存(上):“4毫秒”究竟值多少钱?_Line_05


取 Block 地址的低位,就能得到对应的 Cache Line 地址,除了 21 号内存块外,13 号、5 号等很多内存块的数据,都对应着 5 号缓存块中。既然如此,假如现在 CPU 想要读取 21 号内存块,在读取到 5 号缓存块的时候,我们怎么知道里面的数据,究竟是不是 21 号对应的数据呢?同样,建议你借助现有知识,先自己思考一下,然后再看我下面的分析,这样会印象比较深刻。


在对应的缓存块中,我们会存储一个 组标记 (Tag)。这个组标记会记录,当前缓存块内存储的数据对应的内存块,而缓存块本身的地址表示访问地址的低 N 位。就像上面的例子,21 的低 3 位 101,缓存块本身的地址已经涵盖了对应的信息、对应的组标记,我们只需要记录 21 剩余的高 2 位的信息,也就是 10 就可以了。


有效位


CPU 在读取数据的时候,并不是要读取一整个 Block,而是读取一个他需要的数据片段。这样的数据,我们叫作 CPU 里的一个字(Word)。具体是哪个字,就用这个字在整个 Block 里面的位置来决定。这个位置,我们叫作偏移量(Offset)。


一个内存的访问地址,最终包括高位代表的组标记、低位代表的索引,以及在对应的 Data Block 中定位对应字的位置偏移量。



37 | 高速缓存(上):“4毫秒”究竟值多少钱?_Line_06


内存地址到 Cache Line 的关系


由“ 索引 + 有效位 + 组标记 + 数据 ”组成。如果内存中的数据已经在 CPU Cache 里了,那一个内存地址的访问,就会经历这样 4 个步骤:



根据内存地址的低位,计算在 Cache 中的索引;



判断有效位,确认 Cache 中的数据是有效的;



对比内存访问地址的高位,和 Cache 中的组标记,确认 Cache 中的数据就是我们要访问的内存数据,从 Cache Line 中读取到对应的数据块(Data Block);



根据内存地址的 Offset 位,从 Data Block 中,读取希望读取到的字。


如果在 2、3 这两个步骤中,CPU 发现,Cache 中的数据并不是要访问的内存地址的数据,那 CPU 就会访问内存,并把对应的 Block Data 更新到 Cache Line 中,同时更新对应的有效位和组标记的数据。


好了,讲到这里,相信你明白现代 CPU,是如何通过直接映射 Cache,来定位一个内存访问地址在 Cache 中的位置了。其实,除了直接映射 Cache 之外,我们常见的缓存放置策略还有全相连 Cache(Fully Associative Cache)、组相连 Cache(Set Associative Cache)。这几种策略的数据结构都是相似的,理解了最简单的直接映射 Cache,其他的策略你很容易就能理解了。


减少 4 毫秒,公司挣了多少钱?


刚才我花了很多篇幅,讲了 CPU 和内存之间的性能差异,以及我们如何通过 CPU Cache 来尽可能解决这两者之间的性能鸿沟。你可能要问了,这样做的意义和价值究竟是什么?毕竟,一次内存的访问,只不过需要 100 纳秒而已。1 秒钟时间内,足有 1000 万个 100 纳秒。别着急,我们先来看一个故事。


2008 年,一家叫作 Spread Networks 的通信公司花费 3 亿美元,做了一个光缆建设项目。目标是建设一条从芝加哥到新泽西,总长 1331 公里的光缆线路。建设这条线路的目的,其实是为了将两地之间原有的网络访问延时,从 17 毫秒降低到 13 毫秒。


你可能会说,仅仅缩短了 4 毫秒时间啊,却花费 3 个亿,真的值吗?为这 4 毫秒时间买单的,其实是一批高频交易公司。它们以 5 年 1400 万美元的价格,使用这条线路。利用这短短的 4 毫秒的时间优势,这些公司通过高性能的计算机程序,在芝加哥和新泽西两地的交易所进行高频套利,以获得每年以 10 亿美元计的利润。现在你还觉得这个不值得吗?


其实,只要 350 微秒的差异,就足够高频交易公司用来进行无风险套利了。而 350 微秒,如果用来进行 100 纳秒一次的内存访问,大约只够进行 3500 次。而引入 CPU Cache 之后,我们可以进行的数据访问次数,提升了数十倍,使得各种交易策略成为可能。


总结延伸


很多时候,程序的性能瓶颈,来自使用 DRAM 芯片的内存访问速度。


根据摩尔定律,自上世纪 80 年代以来,CPU 和内存的性能鸿沟越拉越大。于是,现代 CPU 的设计者们,直接在 CPU 中嵌入了使用更高性能的 SRAM 芯片的 Cache,来弥补这一性能差异。通过巧妙地将内存地址,拆分成“索引 + 组标记 + 偏移量”的方式,使得我们可以将很大的内存地址,映射到很小的 CPU Cache 地址里。而 CPU Cache 带来的毫秒乃至微秒级别的性能差异,又能带来巨大的商业利益,十多年前的高频交易行业就是最好的例子。


在搞清楚从内存加载数据到 Cache,以及从 Cache 里读取到想要的数据之后,我们又要面临一个新的挑战了。CPU 不仅要读数据,还需要写数据,我们不能只把数据写入到 Cache 里面就结束了。下一讲,我们就来仔细讲讲,CPU 要写入数据的时候,怎么既不牺牲性能,又能保证数据的一致性。


标签:缓存,访问,Cache,37,毫秒,内存,数据,CPU,高速缓存
From: https://blog.51cto.com/u_15202985/6324716

相关文章

  • 【蓝桥杯集训·每日一题】AcWing 3728. 城市通电
    写在前面本人CSDN博客主页:这里一、题目1、原题链接3728.城市通电2、题目描述平面上遍布着n座城市,编号1∼n。第i座城市的位置坐标为(xi,yi)。不同城市的位置有可能重合。现在要通过建立发电站和搭建电线的方式给每座城市都通电。一个城市如果建有发电站,或者通过电线直接或间......
  • POJ1737 Connected Graph ( n点无向连通图计数
    题意说明:求\(n\)个点的无向连通图个数据说已经非常典了,但是我太菜了不会组合数学,最近补档时看到这道题,决定记录下来理理思路......
  • 力扣---1372. 二叉树中的最长交错路径
    给你一棵以 root 为根的二叉树,二叉树中的交错路径定义如下:选择二叉树中任意 节点和一个方向(左或者右)。如果前进方向为右,那么移动到当前节点的的右子节点,否则移动到它的左子节点。改变前进方向:左变右或者右变左。重复第二步和第三步,直到你在树中无法继续移动。交错路径的长度......
  • P1937 [USACO10MAR]Barn Allocation G
    BarnAllocationG题目描述农夫约翰最近开了一个新的牲口棚屋,并且现在接受来自奶牛的分配畜栏请求因为其中的一些畜栏有更好风景。畜栏包括N个畜栏(1≤N≤100,000),方便起见,我们把它们编号为1..N,畜栏i能容纳Ci只牛(1≤Ci≤100,000),第i只牛需要连续编号畜栏(从Ai到Bi)来漫......
  • 37基于java的职工管理系统设计与实现
    本章节给大家带来一个基于java的职工管理系统设计与实现,可适用于员工管理系统,企业员工管理系统,公司员工管理系统,企业人事管理系统,基于java职工管理系统,前后端分离,员工考勤管理系统,职工奖惩管理系统,职员合同管理,HR管理系统,人事HR管理系统等;引言由于计算机的快速发展,企业员工管......
  • i7 13700f和i7 13700kf区别 i713700f和i713700kf参数对比
    酷睿i7-13700F采用intel7制程工艺性能核+能效核的高性能混合构架,拥有16核24线程,包括8个性能核心以及8个能效核心,最高睿频可达5.2GHz,同时配备24MBL2缓存以及36MBL3缓存,原生支持DDR5-5600、5200高频内存,无内置核显,需搭配独立显卡使用组装电脑选i713700f还是i713700kf怎么搭配......
  • 刷题笔记:Luogu P3743
    题目传送门Solution最多能将这些设备一起使用多久,显然答案满足单调性(如果\(x<y\)而不能使用\(x\)时间则一定不能使用\(y\)时间)通俗一点,就是前边的时间不满足则后边一定不满足,也就是局部答案舍弃性,考虑二分时间至于check怎么写呢?和奶牛晒衣服有异曲同工之妙,若设二分出来的时间......
  • day44| 518+377
    518.零钱兑换II 题目简述:给你一个整数数组coins表示不同面额的硬币,另给一个整数amount表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回0。假设每一种面额的硬币有无限个。 题目数据保证结果符合32位带符号整数。......
  • 「解题报告」P3703 [SDOI2017]树点涂色
    有趣题,代码超好写,而且思路超有趣!!!首先发现操作1的操作都是某个点到根,不难发现这样每种颜色一定对应着树上的一条链。那么操作2可以直接树上查分求答案,这样我们只需要考虑维护每个点到根的链的数量了。怎么维护链的数量?发现这个操作1长得和LCT的Access操作一模一样啊,所......
  • 37、list与Set区别
    (1)List简介实际上有两种List:一种是基本的ArrayList,其优点在于随机访问元素,另一种是LinkedList,它并不是为快速随机访问设计的,而是快速的插入或删除。ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。LinkedList:对顺序访问进行了......