您的位置  > 互联网

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

首先,你需要知道如何理解Cache这个词。 百度百科对Cache的定义是高速缓冲存储器。 由此我们可以知道,Cache是​​计算机存储的一种。 计算机系统中的存储有很多类别。 那么这里的缓存属于哪一类,属于什么状态呢? 要知道答案,你可以从下图中看出一些端倪。

从图中可以看到Cache有三种类型,L1~L3,也就是传说中的三级Cache。

从价格上看,越往金字塔顶端的存储越贵; 从速度上看,越往金字塔顶端的存储速度也越快,但存储空间却越来越小。 具体信息规格见下表。 看:

贮存

硬件媒体

单位成本(美元/MB)

随机访问延迟

一般用途尺寸*

一级缓存

静态随机存储器

4纳秒

256KB

静态随机存储器

11纳秒

1MB

静态随机存储器

38纳秒

8MB

动态随机存取存储器

0.015

167纳秒

8GB

磁盘

固态硬盘/闪存

0.0004

150us

512GB

磁盘

硬盘/硬盘

0.00004

10毫秒

>512GB

从这里可以看出Cache是​​一个非常昂贵、速度快但是很小的东西。

如果想获取其他信息,可以在Linux中使用一些命令来查看当前系统中的缓存参数:

[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计算的大量数据都存储在内存(以下统称内存)中,那么两者之间的交互一定非常频繁。 如果CPU和内存直接相连,那么它们就不能高速工作,因为CPU要花很多时间等待内存。 那么这个时候就需要一个中间人来协调,而这个协调就是缓存的工作。

我们看一下三级缓存在系统中的位置:

从上图我们可以看出Cache在结构上与内存是如何关联的。 图为多核CPU。 每个核心都有独占缓存(分为数据缓存和指令缓存),但所有核心共享缓存。

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

CPU寄存器读取数据时,并不直接去内存获取。 相反,它首先请求数据。 如果没有数据,它会要求提供数据。 如果仍然没有数据,则继续执行,直到内存中读取到所需的数据。

假设有一段代码有两个变量a和b,这两个变量在内存中是连续的。 当代码要读写变量a时,Cache会预先从内存中加载一条Cache Line的数据到缓存中,这是指缓存每次从内存中加载的数据大小。 这里我们需要对a变量进行读写,所以缓存不仅仅从内存中获取a的一个变量,而是从a的起始点读取一个大小的数据到缓存中。 ab 变量在内存中是相邻的,因此 b 变量也会被加载到缓存中。

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

学习完上面的概念后,用一个简单的案例来了解一下如果Cache Miss次数过多会发生什么情况。 看下面的代码,定义一个全局数组,大小为2048*2048*4==,使用两种不同的方法遍历赋值,检查其运行时间。

#include
#include 
#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]时,根据分析,大小为32KB和64B。 代码中第一个结果是按照内存顺序循环的,因为a[0][0]~a[0][16]是1,a[0][0]~a[3][2047]是size,所以循环16次后才出现1个,循环8192次后才出现1个。 没有找到数据,需要去下一级缓存。 获取数据的情况。

同样的数字a[0][0]~a[0][16]都是1,但是在循环中每次都会出现。 循环4次后,L1中找不到需要的数据,即a[0][0]、a[1][0]、a[2][0]、a[3][0],直到赋值a[4][0]时,从二级缓存中获取新数据。 因此,需要更多的机器周期来从内存或二级缓存获取数据。 每次操作都是一样的,这也是花费更多时间的根本原因。

至此,我们对Cache有了一个简单的了解。 以前我们感觉三级缓存似乎离我们的编码很遥远,甚至是遥不可及的东西。 当我们对这个有了一定的了解之后,我们发现它是一直可用的。 它影响我们的表现。 如果我们在编码过程中有意识地避免,提高缓存命中率,那么代码性能将会提高很多。