cache高速缓存 简单讲解与验证

前言

上次做计算机系统的实验,做到一个高速缓存的实验,不过实验内容比较简单,就是验证一下不同的数据存取方式对缓存命中率的影响(通过运行时间体现)

因为我上课全程摸鱼,所以今天来回顾一下高速缓存这个内容,加深映像(

高速缓存介绍

什么是高速缓存

高速缓存是介于cpu和内存之间的一级存储器。访问cpu高速缓存的速度要快于访问内存,因此高速缓存常常用来加速cpu的运算。

你可以在任何一本介绍计算机基础的课本上找到类似的图(图片源 百度图片)来描述计算机的各种存储器:
在这里插入图片描述

高速缓存基本原理

cpu的速度远远快于内存,因此如果cpu只是从内存中读取数据,那么会花费较多的时间在等待数据上,我们希望有一种方法解决【从内存中读数据慢】的问题,于是有了高速缓存。

对于数据的读取基于两个猜想,假设我们读取内存地址x处的数据,那么有

  1. 内存地址x处的数据在短时间内容易被再次访问
  2. 内存地址x周围的数据在短时间内容易被再次访问

以上两个猜想很容易得到证实,(这也是程序的局部性原理的一部分),比如我们会经常编写这样的代码

// 猜想1: 频繁地访问指针a指向的地址存储的变量
int* a = new int;
*a = 123;
for(int i=0; i<n; i++)
{
	*a += i;
} 

// 猜想2: 矩阵运算的时候 频繁访问相邻的变量 a[i+1]
int a[10];
int b[10];
for(int i=0; i<9; i++)
{
	a[i] += b[i+1];
	a[i+1] += b[i];
}

频繁访问内存意味着速度慢。高速缓存的引入解决了这个问题

hit

我们在访问一个内存地址的时候,先查看高速缓存里面有无该地址的数据。如果有就直接从高速缓存中读取数据,这个行为我们叫做hit,即缓存命中

miss

如果高速缓存中没有数据,我们需要从内存中读取数据,这个行为叫做miss。值得注意的是,我们一次读取不是读取一个内存单元的数据,而是读取一个【cache行】的数据,这意味着目标地址及其附近区域的一些数据会被读到高速缓存中,如果下次访问邻近的数据,就不会miss。(cache行一般是64字节)

图解

下面通过一张图解释高速缓存的基本原理,这个原理告诉我们,对内存中的数据的访问,数据们相距的越近,数据越集中,存取速度越快。

在这里插入图片描述

更加详细

在《深入理解计算机系统》书中,给出了相当详细的对于高速缓存的解释,我们可以来一探究竟

在这里插入图片描述
在这里插入图片描述

简单的来说,就是取数据的时候,先查看数据地址,然后做一次硬件哈希映射,查找缓存里是否命中,然后按照情况做对应的操作。

简单实验验证

我们模拟简单的矩阵乘法,编写两个子程序,计算res=a*b的矩阵乘法,我们使用一维数组来模拟n*n大小的矩阵

其中1是正常计算,即按行访问a矩阵的元素,然后按列访问b矩阵的元素,最后对应位置相乘,写入res矩阵。
在这里插入图片描述

void matrix_multiply_1(int* m1, int* m2, int* res, int n)
{
	for(int i=0; i<n; i++)
		for(int j=0; j<n; j++)
			for(int k=0; k<n; k++)
				res[i*n+j] += m1[i*n+k]*m2[i*k+j];
}

我们将1中的循环顺序替换一下,由ijk变为ikj,性能大提升!

void matrix_multiply_2(int* m1, int* m2, int* res, int n)
{
	for(int i=0; i<n; i++)
		for(int k=0; k<n; k++)
			for(int j=0; j<n; j++)
				res[i*n+j] += m1[i*n+k]*m2[i*k+j];
}

可以看到在3000x3000的规模下 1用时174s,而2用时仅仅115s
在这里插入图片描述

你可能觉得总的循环次数不变,为何速度相差这么大?

如果k在最内层,变化频次最高,这意味着大量相隔很远的数据(因为以一维数组形式模拟矩阵在内存中的存放)将会被频繁读入cache中,而cache行存储的是邻近的几个数据

后一次访问的地址,不在前一次访问带来的cache存储的范围内,当然miss率高

在这里插入图片描述

下面两幅图对比了两种存取方式,黄色方块是我们矩阵乘法访问的元素,红色方块是访问目标元素时带来的cache行存储。

方式1:每一列都要miss
在这里插入图片描述

方式2:每读取完一个cache行,仅仅在首元素发生miss,其他元素均随着首元素的读入而进入cache
在这里插入图片描述

代码

#include <bits/stdc++.h>

using namespace std;

void matrix_multiply_1(int* m1, int* m2, int* res, int n)
{
	for(int i=0; i<n; i++)
		for(int j=0; j<n; j++)
			for(int k=0; k<n; k++)
				res[i*n+j] += m1[i*n+k]*m2[i*k+j];
}

void matrix_multiply_2(int* m1, int* m2, int* res, int n)
{
	for(int i=0; i<n; i++)
		for(int k=0; k<n; k++)
			for(int j=0; j<n; j++)
				res[i*n+j] += m1[i*n+k]*m2[i*k+j];
}

int main()
{ 	
	double t1, t2;
	clock_t st, ed;
	int n;
	cin>>n;
	
	int* m1 = new int[n*n];
	int* m2 = new int[n*n];
	int* res = new int[n*n];
	
	for(int i=0; i<n; i++)
		for(int j=0; j<n; j++)
			m1[i*n+j]=rand()%1000;
			
	for(int i=0; i<n; i++)
		for(int j=0; j<n; j++)
			m2[i*n+j]=rand()%1000;
			
	memset(res, 0, sizeof(res));
	st = clock();
	matrix_multiply_1(m1, m2, res, n);
	ed = clock();
	t1 = (double)(ed-st) / CLOCKS_PER_SEC;
	
	memset(res, 0, sizeof(res));
	st = clock();
	matrix_multiply_2(m1, m2, res, n);
	ed = clock();
	t2 = (double)(ed-st) / CLOCKS_PER_SEC;
	
	cout<<t1<<endl<<t2<<endl;
	
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章