[計算機操作系統]從內存的時間局部性與空間局部性看多重循環的代碼優化

斷斷續續刷完了卡耐基梅隆大學的計算機操作系統的課,感覺獲益匪淺,作爲非CS科班出身的程序員,工作之餘深感自己關於CS知識儲備不足,而計算機操作系統是內功修煉。這門面向本科生的課詳細全面的介紹了計算機操作系統,主講人就是《深入理解計算機操作系統》這本書的作者布萊恩特教授。Youtobe上有他們完整學期課程的視頻,現場錄製的。強烈推薦有空閒時間能夠翻牆的朋友去上上這門課,效果真的比自己看書好太多倍了。傳送門:
CMU CS213 Youtube地址
作爲自己的學習筆記,我將在後續整理出一些我覺得可以反覆咀嚼的章節與大家分享。話不多說,進入正題。
我們可能都知道在寫多重循環的時候要內大外小,訪問二維數組儘量按行訪問。可是爲什麼要這麼做呢?這麼做的意義在哪裏呢?內存的時間局部性與空間局部性原理給了我們答案。
Locality, 內存局部性原理:程序傾向於使用地址接近或等於最近已使用的數據和指令地址。
Temporal locality (時間局部性):最近引用過的數據很有可能在將來繼續使用。
在這裏插入圖片描述
Spatial locality (空間局部性):相鄰地址的數據在一段時間內傾向於被再次使用。
例如CPU 某次訪問數組a[0], a[1],那麼下次他很有可能訪問元素a[2];
在這裏插入圖片描述
定義比較抽象,讓我們來看一個例子:

sum = 0;
for( i = 0; i <n; i++)
 	 sum += a[i];
return sum;

從內存的角度看,數組元素a[i]的訪問具有空間局部性(訪問相鄰地址數據),局部變量sum在每一次迭代過程中均被更新,具有時間局部性(同一地址數據重複訪問)。如果將這段代碼翻譯成機器碼,指令也佔用內存空間,從指令集存取的角度看,在循環內順序執行的指令具有空間局部性,而每一次循環則具有時間局部性。
談這個局部性有什麼意義呢?這裏就要涉及到我們計算機的存儲結構與緩存技術。一個典型的計算機存儲結構如下圖所示,
在這裏插入圖片描述
最高的地方L0是CPU寄存器,容量最小存儲速度最快,越往下層容量越大存儲速度越慢。而我們的內存主要由DRAM組成,從上圖中我們可以看出,在內存memory與CPU之間的存儲過程都有緩存cache存在,因爲CPU的處理速度太快,爲了提高運算效率加入緩存技術。DRAM的讀取雙字(16個字節)的時間大約是60ns,SRAM只需要4ns,而從硬盤讀取雙字的時間大約是10ms.
假如我們有一個CPU需要一個數據, 在循環中每次都從內存中讀取,每次都要花費60ns,但如果我們將該數據放入緩存中,每次循環的讀取時間只需要4ns, 少了15倍!如果我們每次所需要的指令或者數據都從內存中讀取,對CPU的資源是 極大的浪費,如果我們將程序可能常用的數據放入緩存中,CPU讀取的效率將提高很多。
DRAM讀取過程:
DRAM即是我們的內存,那CPU是如何從DRAM上存取數據的呢?如下圖所示,在這裏插入圖片描述一個16X8的的DRAM芯片,上面有4*4個核,每個核可以存儲8個bit,CPU訪問內存時候根據一個2bit的地址訪問某一核。例如CPU要訪問(2,1)這個單元,過程如下:
step1: 根據行地址RAS(2),將行號爲2的數據都放入緩衝區中。
在這裏插入圖片描述
step2: 再根據列地址CAS(1)選擇核單元(2,1),將數據從緩存中取出最終返回給CPU。這時請注意,第二行整行的數據其實都已經被放入了緩存,雖然並沒有都使用,但根據空間局部性原理,附近地址的數據很有可能在下一次被繼續訪問到。
在這裏插入圖片描述

緩存模型
在這裏插入圖片描述
這是一個緩存模型,這裏有兩個概念,一個是hit,一個是Miss。
Hit: CPU 需要數據14,並且在緩存中找到了14,則將直接讀取,這一次過程稱爲Hit(擊中)。
在這裏插入圖片描述
Miss:CPU在緩存中沒有找到該數據,進而在內存中尋找,並把他放入緩存中這一過程,成爲一次Miss.
在這裏插入圖片描述
這個時候再回過頭來看我們時間局部性與空間局部性
時間局部性,第二次到第N次訪問相同的地址的數據將被擊中。
在這裏插入圖片描述
空間局部性: 緩存單元裏包含有多個字段,當緩存拿到了第一個字段後,第二到第N個字段都會被擊中, 這個在DRAM讀取過程中我們已經能夠看到。
在這裏插入圖片描述
瞭解到這些,作爲程序員的我們就要知道在寫代碼時儘量去寫緩存友好的代碼。在多重循環中將注意力放到最內層的代碼段優化代碼進而提高內存使用效率。,因爲最內層循環的代碼是運行次數最多的代碼段,根據時間局部性原:
1.儘量保證最深層循環的次數是最多的。
2.儘量使用可重複而不是新定義的變量;

例如,
雙重循環A,

for(i = 0; i< 1000;i++)
{
	for(j =0;j<2;j++)
	{
		//業務代碼
	}
}

雙重循環B

for(j= 0; j< 2;j++)
{
	for(i =0;i<1000;j++)
	{
		//業務代碼
	}
}

同樣是執行了2000次,B就比A好,因爲B利用到時間局部性原則,
空間局部性原則:
儘量訪問在內存中存儲結構步長爲1的數據結構;
這個在二維數組的訪問中很常見,因爲二維數組是按行存儲,如果按行訪問,步長就是1,如果按列訪問,步長就是行數目,失去了空間局部性。如果我們訪問一個二維數組
方法A

for(i=0;i <N;i++)
	sum+=a[0][i];

方法B:

for(i=0;i <N;i++)
	sum+=a[i][0];

方法A顯然比B好,因爲A利用到了緩存的空間局部性原則,方法B卻沒有,他的緩存miss rate 相對於A將會大大增加,降低效率。
我們再來看一個例子,兩個矩陣AB的乘積存儲在矩陣C。
方法A
由上圖我們可以看出,方法一和方法二在內層循環中矩陣B均沒有利用到空間局部性原則,而方法三矩陣AB均是按照行讀取,利用到了空間局部性原理,效率不言而喻。當矩陣維度超過300時程序運行時間會有明顯的不同。關於空間局部性原則我們只做了定性分析,緩存的hit rate與miss rate是可以做定量分析的,有興趣的朋友可以查閱相關資料,這裏就不做贅述了。

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