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;
}

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