我是做C语言方面的开发工作,进入公司以来,经常需要做性能优化方面的工作,被一些性能问题给折磨的要si要活的。在想,在不允许替换更强的cpu前提下,到底怎么样才能发挥CPU的最强性能呢?这个问题一直困扰我,直到有一天不知道在哪听到还是看到一句话(大概是这个意思):“如果把cache优化的差不多了,那么性能就几乎没有什么可优化的空间了。”听到这句话后,文化程度不高的我立马拍手叫绝,惊呼WOCAO!一下引起我的好奇心,到底什么是Cache呢?

首先要知道Cache这个词怎么理解,Cache百度百科给的定义就是高速缓冲储存器。这样可以知道,Cache属于计算机储存器的一种,计算机系统中储存器分为很多类,那这里的cache又属于哪类呢,属于一个什么地位?要知道这个答案可从下图看出些许端倪。

从图中可以看到Cache有L1~L3三种,这就是传说中的三级缓存。

从价格来看,越往金字塔顶层的储存器越贵;从速度来看,越往金字塔顶层储存速度也是越快的,而储存空间却是越来越小,具体的信息规格可以从下表中看出:

储存器

硬件介质

单位成本(美元/MB)

随机访问时延

一般使用大小*

L1 Cache

SRAM

7

4ns

256KB

L2Cache

SRAM

7

11ns

1MB

L3Cache

SRAM

7

38ns

8MB

Memory

DRAM

0.015

167ns

8GB

Disk

SSD/NAND

0.0004

150us

512GB

Disk

HDD/HHD

0.00004

10ms

>512GB

到这里能看出来Cache是个很贵很快但很小的东西。

如果还想获得其他信息在linux中可以用一些命令来查看当前系统中的cache参数:

[root@angie ~]# cd /sys/devices/system/cpu/;ll         #查看当前系统多少cpu多少核,当前系统有2个cpu核
总用量 0
drwxr-xr-x. 5 root root    0 3月   7 15:06 cpu0
drwxr-xr-x. 5 root root    0 3月   7 15:06 cpu1
...
cat cpu0/cache/index0/level           #查看缓存属于哪级
cat cpu0/cache/index0/size             #查看当前等级缓存大小
cat cpu0/cache/index0/type             #查看缓存类型:Data(数据缓存),Instruction(指令缓存),Unified
cat cpu0/cache/index0/shared_cpu_list   #查看缓存被哪些cpu核共享使用
cat cpu0/cache/index0/coherency_line_size  #查看Cache一次载入数据的大小,即Cache Line的大小

那么它在计算机中所做的工作是什么呢?

众所周知,计算机cpu的处理速度是非常快的,比内存要快很多倍,然而CPU计算的大量数据都是保存在memery内存(后面统称为内存),那么两者之间交互必然是非常频繁的,如果CPU和内存直接相连工作,必然不能进行高速工作,因为cpu的大量时间都用来等待内存了。那么这时候就需要中间商来进行协调,这种协调工作就是cache的工作。

再看看三级缓存在系统中的位置:

从上图可以看出来Cache在结构上和内存是怎样一个存在关系,图中画的是一个多核cpu,每个核都有一个独享的L1Cache(L1Cache分为数据缓存和指令缓存)和L2Cache,而L3Cache是所有的核共享缓存。

接下来从编码层面分析三级缓存是如何工作的,这里面会涉及到几个重要基本概念——Cache Line, CacheMiss, CacheHit。

cpu寄存器读取数据时,不是直接去内存获取,而是首先向L1Cache中要数据,如果L1Cache没有数据,则向L2Cache要数据,如果仍然没有,再往下,直到去内存中读到所需数据。

假设有段代码中有a和b两个变量,在内存中是连续的。当代码要对变量a进行读写操作,Cache提前从内存中加载一个Cache Line的数据到缓存中,CacheLine是指缓存从内存中每次加载的数据大小。这里要读写a变量,那么缓存从内存不仅仅是获取一个a的变量,而是从a起始的地方开始往后读取一个CacheLine大小的数据到cache中,ab变量在内存中相邻,因此b变量也会一并被加载到缓存中。

这时候如果需要读写b变量,发现b变量就在当前cache中,就不会再花费几百纳秒的时间去内存中读取了,直接获取缓存中的b变量即可,这个情况就是CacheHit。相反,如下图如果发现即将操作的c变量不在cache中,这个情况就是CacheMiss,那么就需要重新去内存或者下一级cache中加载数据,这个过程就相当的耗费时间。

学习以上的概念后,再结合一个简单案例来了解如果Cache Miss过多会怎么样。见如下代码,定义一个全局数组,大小为2048*2048*4 = 16777216Byte =16384KB ,使用两种不同的方式去遍历赋值,查看其运行时间。

#include<stdio.h>
#include <time.h>#define N       2048
int a[N][N] = {0};          //全局变量void main(void)
{int i,j;struct timespec time_start={0,0},time_end={0,0};clock_gettime(CLOCK_REALTIME,&time_start);for (i = 0; i < N; ++i)for (j = 0; j < N; ++j)       a[i][j] = 1;         //先修改行,再修改列clock_gettime(CLOCK_REALTIME,&time_end);printf("first : %llus, %lluns\n", time_end.tv_sec-time_start.tv_sec, time_end.tv_nsec-time_start.tv_nsec);clock_gettime(CLOCK_REALTIME,&time_start);for (i = 0; i < N; ++i)for (j = 0; j < N; ++j)     a[j][i] = 1;        //先修改列,再修改行clock_gettime(CLOCK_REALTIME,&time_end);printf("second: %llus, %lluns\n", time_end.tv_sec-time_start.tv_sec, time_end.tv_nsec-time_start.tv_nsec);
}

运行结果:

从运行结果可以看出,第二种运行时间明显花费比第一种要多。知其然更要知其所以然,那么到底为什么导致这样的一个结果呢?

我们知道在内存的空间上,a[0][0] ~ a[0][N]内存是连续的,如下图显示那样。

在读取变量a[0][0]的时候,按照L1Cache大小为32KB,CacheLine为64B来分析,代码中的first结果是按内存顺序循环的,因为a[0][0]~a[0][16]为一个CacheLine,a[0][0]~a[3][2047]为一个L1Cache大小,所以循环16次才会出现一个CacheMiss,循环8192次才会出现L1Cache中没有找到数据,需要去下一级缓存中拿数据的情况。

而second的中同样是a[0][0]~a[0][16]为一个CacheLine,但是每次循环都会出现CacheMiss。循环4次L1中就无法找到所需数据,即a[0][0],a[1][0],a[2][0],a[3][0],到赋值a[4][0]时候就向二级缓存获取新的数据,结果需要更多的机器周期去内存或二级缓存获取数据。每次操作都是CacheMiss,这既是second需要消耗更多时间的根本原因。

到这里就有了一个对Cache的简单认知,以前觉得三级缓存好像离我们编码很远,甚至八竿子打不着的东西,当我们对这个有一定的了解之后,才发现它无时不刻在影响着我们的性能,如果在编码的过程中有意识的规避CacheMiss,提高Cache命中率,那么代码性能将会提升很多

要了解Cache更多的知识可以关注以下公众号查看哦~~~

初步理解三级缓存Cache相关推荐

  1. SpringBean依赖和三级缓存

    spring中的bean依赖有大体上可以分为两类,共3中形式,下面简单介绍一下. 第一类是构造方法中的循环依赖,这种会报错 @Service public class ServiceA {privat ...

  2. 计算机缓存Cache机制理解

    1.计算机存储体系简介 存储器是分层次的,离CPU越近的存储器,速度越快,每字节的成本越高,同时容量也因此越小.寄存器速度最快,离CPU最近,成本最高,所以个数容量有限,其次是高速缓存(缓存也是分级, ...

  3. 从Java视角理解CPU缓存(CPU Cache)

    http://coderplay.iteye.com/blog/1485760 众所周知, CPU是计算机的大脑, 它负责执行程序的指令; 内存负责存数据, 包括程序自身数据. 同样大家都知道, 内存 ...

  4. android分享图片功能实现原理,Android:简单实现并理解图片三级缓存

    学习Android网络开发的过程中,势必会经历很多痛苦的过程,其中一个大坑就是图片缓存,当然现在有很多现成的库非常方便,常常几行代码就可以实现想要的功能,但不懂其中的原理是不行的,所以对于刚开始学习网 ...

  5. CPU三级缓存技术解析

    CPU三级缓存技术解析 cpu存取数据 cpu存取数据大致可以认为是下图的流程(此处图比较简单) cpu拿到需要的内存地址,之后这个地址会被mmu转换成真正的物理地址,接下来会去查接下来查L1 cac ...

  6. Spring是如何利用“三级缓存“巧妙解决Bean的循环依赖问题

    前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对方,最终形成闭环.用一副经典的图示可以表示成这样(A.B.C都代表对象,虚线代表引用关系): 注意:其实可以N=1,也就 ...

  7. 【转】七个例子帮你更好地理解 CPU 缓存

    我的大多数读者都知道缓存是一种快速.小型.存储最近已访问的内存的地方.这个描述相当准确,但是深入处理器缓存如何工作的"枯燥"细节,会对尝试理解程序性能有很大帮助. 在这篇博文中,我 ...

  8. Spring三级缓存解决循环依赖

    1. 前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对方,最终形成闭环.用一副经典的图示可以表示成这样(A.B.C都代表对象,虚线代表引用关系): 其实可以N=1,也就 ...

  9. CPU 与 Memory 内存之间的三级缓存的实现原理

    title: CPU Cache date: 2019-11-17 20:20:30 keywords: cache "CPU cache" "三级缓存" 缓存 ...

最新文章

  1. Exchange Server2013 系列十:证书的配置
  2. Linux下eclipse及mysql安装,c++访问mysql数据库
  3. Python3-onvif协议之相机截图
  4. ERROR Worker: All masters are unresponsive! Giving up
  5. 【AES图像加解密】基于AES图像加解密算法的MATLAB仿真
  6. 日本比中国快一个小时,泰国比中国慢一个小时
  7. c++ 2.常量定义
  8. 文件的创建与读取 文件的数据添加
  9. eclipse提示方法已过时_提高效率,eclipse上你可能不知道的技巧
  10. 类string的构造函数、拷贝构造函数和析构函数
  11. 怎么new一个指针_C++知识点 34:指针运算符重载 -- 智能指针
  12. java 32 64 性能,Java 64位的性能是否优于32位版本?
  13. 办公软件入门--word01
  14. web前端html代码,WEB前端--HTML(示例代码)
  15. 2020年Java市场需求分析
  16. 信息安全常见名词解释
  17. MOOC有效沟通技巧答案(全)
  18. PyTorch - 27 - 带PyTorch的CNN Confusion Matrix - 神经网络编程
  19. L2行情接口怎么用最高效?
  20. masonry布局出现 'couldn't find a common superview for...报错解决办法

热门文章

  1. c语言如何自动填写验证码,小程序 自带样式的验证码自动填入
  2. 删除计算机用户时拒绝访问权限,Win7系统删除提示文件夹访问被拒绝,您需要权限来执行操作解决方法...
  3. android usb 开发:如何跳过usb权限询问,解决绕过android下apk使用usb设备权限查询相应问题,自动获取usb权限...
  4. 关于激光雷达传感器分类及简介
  5. API安全防护解决方案
  6. 如何为你的WordPress博客帖子找到免版税图片
  7. OpenJudge 1.6.2
  8. To_Date函数用法(转)
  9. 运动和不运动的人,人生到底差在哪
  10. Android Jetpack