1,SRAM高速缓存的结构

获取本机CPU的SRAM缓存信息

我使用的是一个叫cpuinfo_x86的小程序,可以获取x86架构的cpu相关信息。下载地址:http://osxbook.com/book/bonus/misc/cpuinfo_x86/cpuinfo_x86.c
gcc编译后运行,以下是当前测试环境的SRAM信息:

L1 Instruction Cache //L1指令缓存
Size : 32K
Line Size : 64B
Sharing : shared between 2 processor threads
Sets : 64
Partitions : 1
Associativity : 8

L1 Data Cache //L1数据缓存
Size : 32K
Line Size : 64B
Sharing : shared between 2 processor threads
Sets : 64
Partitions : 1
Associativity : 8

L2 Unified Cache //L2指令数据缓存
Size : 256K
Line Size : 64B
Sharing : shared between 2 processor threads
Sets : 512
Partitions : 1
Associativity : 8

L3 Unified Cache //L3指令数据缓存
Size : 3M
Line Size : 64B
Sharing : shared between 16 processor threads
Sets : 4096
Partitions : 1
Associativity : 12

Size是该缓存所有数据块的合计大小,Sets是总组数,Associativity是每组的行数,LineSize是每行的数据块大小。指令和数据缓存只用来缓存指令或内存数据,有利于CPU并行取指和访存提速,Unified Cache可以同时缓存两者,L1L2为每核心独享,L3为多核共享,整体结构大概如下图:

(图1)

内存数据在缓存中的定位与传输

假设有指令要从L1中读一个字节,内存地址为0000000000000000000000000000000000000000000000000000000000000000
由于L1缓存块大小64B,64=2664=2^664=26,所以最后六位用于计算数据块中的字节偏移,称为b位\color{orange}b位b位:
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\color{orange}0000000000000000000000000000000000000000000000000000000000000000000000
组数量也为64,那么可得出组的index为接下来的6位,称为s位\color{red}s位s位:
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\color{red}000000\color{orange}0000000000000000000000000000000000000000000000000000000000000000000000
剩下的位用来寻找该组中存有该数据的行,称为tag标签,t位\color{blue}t位t位:
0000000000000000000000000000000000000000000000000000000000000000\color{blue}0000000000000000000000000000000000000000000000000000\color{red}000000\color{orange}0000000000000000000000000000000000000000000000000000000000000000000000
缓存的控制逻辑硬件解析内存地址后会先用s位定位组,然后并行的用t位定位行。如果找到了组和行既是缓存命中,会用b位来寻找数据位置(或数据起始位置)并返回数据。否则就是缓存不命中,会向下一级缓存读取数据。
每行都有一个数据块,数据块的基本单位为字节,例如L1的数据块为64字节,缓存着内存中0000000000000000000000000000000000000000000000000000000000至 0000000000000000000000000000000000000000000000000000111111的数据。每字节index与b位对应,例如上面要寻找的数据既是在下图行1中的数据块的字节0中:

(图2)

通过这种机制将内存数据分布在SRAM缓存中有点类似于编程中的散列表。内存中每连续n个字节相当于要存储的value,内存地址的s位相当于key,缓存的组相当于桶,每个行相当于node。与散列表不同的是node数量有限制,如果超出数量限制,将会进行不同策略的行替换,另外每个node之间也没有链接关系,所有行用t位进行标记,由硬件进行并行搜索。
s,b,t位由缓存的结构决定,例如测试环境的L3缓存与L2,L1缓存对比:
L1:0000000000000000000000000000000000000000000000000000000000000000L1:\color{blue}0000000000000000000000000000000000000000000000000000\color{red}000000\color{orange}000000L1:0000000000000000000000000000000000000000000000000000000000000000
L2:0000000000000000000000000000000000000000000000000000000000000000L2:\color{blue}0000000000000000000000000000000000000000000000000\color{red}000000000\color{orange}000000L2:0000000000000000000000000000000000000000000000000000000000000000
L3:0000000000000000000000000000000000000000000000000000000000000000L3:\color{blue}0000000000000000000000000000000000000000000000\color{red}000000000000\color{orange}000000L3:0000000000000000000000000000000000000000000000000000000000000000
这种设计使L1,L2,L3每级缓存之间的数据传输有以下的映射关系(组index由0改为1起始):
(图3)

由于内存很大,缓存相对很小,这种映射关系对于正常编程中的数据访问是合理的,但是由于映射关系固定,从L1的角度来看,整个内存地址中有1/64的地址段的数据(对于一个4GB内存来讲既是有64MB的数据)最终都是要缓存到它的某一个组中,如果大量反复访问该范围数据(64MB=1048576个数据块)会造成该组中大量的行替换,增加缓存不命中率,由于行替换会造成访存指令等待,进而导致CPU数据吞吐率降低。

2,缓存读不命中测试

缓存不命中有冷不命中,容量不命中与冲突不命中。缓存在空的状态下,任何读写操作都会造成冷不命中。当工作集(例如循环中要访问的内存地址)整体大小超出该缓存的最大容量时导致的不命中称为容量不命中。
冲突不命中与图3中的映射关系有关,由于内存数据向上一级传输的映射关系是固定的,反复访问内存中的某个子集内的数据会造成大量的不命中,例如根据测试环境,在Unity内进行以下测试:

using System.Collections;
using UnityEngine;public class TestScript : MonoBehaviour
{int[] array1 = new int[10000000];// Use this for initializationvoid Start(){int pTime;int cTime;int sum = 0;Refresh();pTime = System.Environment.TickCount;sum = Sum1(sum);cTime = System.Environment.TickCount;Debug.Log("sum=" + sum);Debug.Log("以步长8192循环:Time:" + (cTime - pTime));Refresh();pTime = System.Environment.TickCount;sum = 0;sum = Sum2(sum);cTime = System.Environment.TickCount;Debug.Log("sum=" + sum);Debug.Log("以步长1循环:Time:" + (cTime - pTime));Refresh();pTime = System.Environment.TickCount;sum = 0;sum = Sum3(sum);cTime = System.Environment.TickCount;Debug.Log("sum=" + sum);Debug.Log("以步长8191循环:Time:" + (cTime - pTime));Refresh();pTime = System.Environment.TickCount;sum = 0;sum = Sum3(sum);cTime = System.Environment.TickCount;Debug.Log("sum=" + sum);Debug.Log("以步长8193循环:Time:" + (cTime - pTime));}void Refresh() {for (int i = 0; i< array1.Length; i++){array1[i] =UnityEngine.Random.Range(1, 100000);}}int Sum1(int sum){for (int i = 0,j = 1; j < 5000000;i = 8192 * (j % 1000),j = j + 1){sum += array1[i];}return sum;}int Sum2(int sum){for (int i = 0, j = 1; j < 5000000; i = 1 * (j % 8192), j = j + 1){sum += array1[i];}return sum;}int Sum3(int sum){for (int i = 0, j = 1; j < 5000000; i = 8191 * (j % 1000), j = j + 1){sum += array1[i];}return sum;}int Sum4(int sum){for (int i = 0, j = 1; j < 5000000; i = 8193 * (j % 1000), j = j + 1){sum += array1[i];}return sum;}
}

对一个一千万长的int数组(38.15MB)根据不同的步长进行50万次循环,测试结果:
以步长8192循环:Time:330
以步长1循环:Time:84
以步长8191循环:Time:95
以步长8193循环:Time:85

可以看出8192对于测试机来讲是一个神奇的数字,其原因可以从该机CPU的缓存结构中找出。int长度为4个字节32位,8192*4B=32768B=32KB,32KB正好是L1缓存的大小,也既是循环中的所有数据最终都要缓存到L1的组0,CPU对L1的所有访存指令都会造成不命中(除非L1在第一次缓存满八行后只对组中同一行进行行替换,那么在1000次循环中会有7次命中),同一个循环中L2与L3相对L1不命中率会稍微低一些,但是数据从内存到L3的时间会更慢,每次不命中造成的时间惩罚相对L1会更高,对整体的时间惩罚也有相当大的影响。

以上研究的都是读不命中的问题,写不命中时有两种策略,写分配与非写分配,写分配 的行为与读类似,会加载下级缓存数据上来直到L1后修改,非写分配 不加载数据而是不断尝试更改下级缓存中的数据。修改缓存数据后对内存数据的更新又分为写回与直写两种,写回的话每行要维护一个修改位,当该行被替换时如果发现有被修改过会对下一级缓存进行更改。直写是修改数据后立刻更新下一级缓存。这些策略是由硬件决定的。大量连续的直写对总线流量(SRAM<—>DRAM)有影响。写分配比较符合局部性的思想,例如对于小步长的循环,一次写不命中后加载数据到L1会避免之后再次的写不命中。

总之,在应用层面来讲,在循环中以步长为1来对数组进行遍历是最快的,而且最新的CPU还有一个prfetching预取机制(以上测试中的CPU没有),既是在循环前尽量提前将内存中的数组数据向上级缓存传输,在循环过程中提前对缓存中的数据进行更新,这是一个针对步长为1的循环的特殊硬件优化,可以规避容量不命中以及其他一些case。另外根据不同CPU对某个2n2^n2n为步长对一个超大的数组进行大量的循环是危险的,有可能会造成大量冲突不命中。

————————————————————————————————————
参考:
深入理解计算机系统—Randal E.Bryant, David R.O’Hallaron

维护日志:
2020-2-2:精简,重构

CPU高速缓存SRAM命中问题的总结与实验相关推荐

  1. 每个程序员都应该了解的 CPU 高速缓存

    每个程序员都应该了解的 CPU 高速缓存 英文原文:Memory part 2: CPU caches 来源:oschina [编者按:这是Ulrich Drepper写"程序员都该知道存储 ...

  2. 【硬件设备】CPU 高速缓存知识

    目录 概述 CPU 的多级缓存 提升L1数据缓存的命中率 提升L1指令缓存的命中率 提升多核 CPU 下的缓存命中率 结论 概述 在计算机系统中,CPU高速缓存(英语:CPU Cache,在本文中简称 ...

  3. java底层知识(3)--CPU 高速缓存

    本文转载自: http://blog.jobbole.com/36263/ 尊重原创 3.CPU的高速缓存 现在的CPU比25年前要精密得多了.在那个年代,CPU的频率与内存总线的频率基本在同一层面上 ...

  4. 每个程序员都应该了解的 CPU 高速缓存【第二部分】

    文章来源 每个程序员都应该了解的 CPU 高速缓存 [编者按:这是Ulrich Drepper写"程序员都该知道存储器"的第二部.那些没有读过第一部 的读者可能希望从这一部开始.这 ...

  5. 每个程序员都应该了解的内存知识-CPU高速缓存

    [编者按:这是Ulrich Drepper写"程序员都该知道存储器"的第二部.那些没有读过第一部 的读者可能希望从这一部开始.这本书写的非常好,并且感谢Ulrich授权我们出版. ...

  6. CPU高速缓存那些事儿

    引言 在分析JDK8新增的高并发原子累加器Striped64的时候,发现有一个"伪共享"的概念,而要理解它必须对CPU缓存有一定的了解,所以本文将先对CPU的缓存架构以及一些相关术 ...

  7. linux内核学习6:Linux的CPU高速缓存cache和页高速缓存cache,buffer

    一.CPU高速缓存(cache) 参考:https://blog.csdn.net/u014470361/article/details/80060701 参考:https://blog.csdn.n ...

  8. 软硬件协同编程 - C#玩转CPU高速缓存(附示例)

    写在前面 好久没有写博客了,一直在不断地探索响应式DDD,又get到了很多新知识,解惑了很多老问题,最近读了Martin Fowler大师一篇非常精彩的博客The LMAX Architecture, ...

  9. 高并发、低延迟之C#玩转CPU高速缓存(附示例)

    写在前面 好久没有写博客了,一直在不断地探索响应式DDD,又get到了很多新知识,解惑了很多老问题,最近读了Martin Fowler大师一篇非常精彩的博客The LMAX Architecture, ...

最新文章

  1. 如何保证MySQL和Redis的数据一致性?
  2. Shell中read的常用方式
  3. python与excel的差别-python对Excel按条件进行内容补充(推荐)
  4. ie8不兼容java项目_[Java教程]ie8以下不兼容document.getElementsByName解决方法
  5. 30段极简Python代码:这些小技巧你都Get了么?
  6. 开始体验Kali Linux
  7. 中国医科大学计算机在线作业,中国医科大学《计算机应用基础》在线作业及参考 答案.doc...
  8. javascript 模块化机制
  9. expander菜单控件_《WPF》Expander控件简单美化
  10. WCF笔记--泛型与集合数据类型
  11. 张樟兴策略分析:数据库营销顾客
  12. MCSA 70-740 windows存储相关的基本概念
  13. 电脑如何重装系统之使用PE优盘启动工具安装win10操作系统
  14. python因子分析法详细步骤_实用干货!因子分析超全步骤总结!
  15. 使用 Vite + 前端框架 (SolidJs,React,Svelte,Vue) 来开发 油猴脚本
  16. 外汇会计-概念-升水(Premium)
  17. win7网上邻居_win7网上邻居寻找教程
  18. 机器人无限火力无限e符文_无限火力快乐玩法:无限击飞机器人
  19. CCleaner软件一键查找/删除重复文件
  20. 洛谷P2058 海港(模拟,优先队列)

热门文章

  1. mysql-8.0.16-winx64.zip安装教程
  2. etag java_你知道HTTP协议的ETag是干什么的吗?
  3. tlc5620输出三角波流程图_TLC5620(电压输出型)_pdf
  4. 【NLP】毕设学习笔记(一):词袋模型、主题模型、词嵌入
  5. rem适配的浏览器_移动端网页布局适配rem方案小结
  6. java命令行参数_Java学习从入门到精通,JDK工具条知识点学习资料
  7. 计算机技术单科线,考研分数线怎么看,计算机专业的,国家线是什么 单科?...
  8. linux 目录权限及归属,Linux中如何设置目录或文件的归属及权限
  9. python中的点的作用_Python基础学习中关键点的作用(三),python,重点,之,函数,3
  10. Python批量下载电子邮件附件并汇总合并Excel文件_如何让繁琐工作自动化:聊聊Python与RPA...