儲存器的層次結構
儲存技術
我們在買電腦時都會關注記憶體、處理器、硬碟等部件的效能,都想記憶體儘可能大,硬碟最好是固態的。
不知道你有沒有遇到過自己寫了大半天的文件,因為不小心突然關機了,自己辛苦忙活了幾個小時的成果又得重寫的情況。可是你是否想過為什麼關機了就會丟失這些資訊呢?為什麼硬碟上的檔案沒有丟?
會丟的那部分資訊肯定是和電有關係的,不然也不會一斷電就丟資訊。記憶體就是這樣的部件,更專業一點的稱呼是隨機訪問儲存器。
隨機訪問儲存器(RAM)分靜態和動態的兩種,靜態 RAM 是將資訊儲存在一個雙穩態的儲存單元裡。什麼叫雙穩態呢?就是隻有兩種穩定的狀態,雖然也有其它狀態,但即使細微的擾動,也會讓它立馬進入一個穩定的狀態。
動態 RAM 使用的是電容來儲存資訊,學過物理的都知道電容這個概念,它很容易就會漏電,使得動態 RAM 單元在 10~100 ms 時間內就會丟失電荷(資訊),但是不要忘記,計算機的執行時間是以納秒計算的,1 GHz 的處理器的時鐘週期就是 1 ns,更何況現在的處理器都不止 1 GHz,所以 ms 相對於納秒來說是很長的,計算機不用擔心會丟失資訊。
動態 RAM 晶片就封裝在記憶體模組中,比記憶體更大的儲存部件是磁碟,發現自己在舊文你真的瞭解硬碟嗎?對磁碟總結的已經不錯了,就直接過渡到區域性性上面去了吧。
區域性性
區域性性通常有兩種不同的形式:時間區域性性和空間區域性性。在一個具有良好時間區域性性的程式中,被引用過一次的記憶體位置很可能在不遠的將來會再被多次引用;同樣在一個具有良好空間區域性性的程式中,如果一個記憶體被引用了一次,那麼程式很可能在不遠的將來引用附近的一個記憶體位置。
不要小看區域性性,區域性性好的程式會比區域性性差的程式執行的更快,要往高階程式設計師走,這是肯定需要了解的。我們選擇把一些常用的檔案從網盤下下來,利用的就是時間區域性性。
下面這段程式碼,再簡單不過,我們僅觀察一下其中的v
向量,向量v
的元素是一個接一個被讀取的,即按照儲存在記憶體中的順序被讀取的,所以它有很好的空間區域性性;但是每個元素都只被訪問一次,就使得時間區域性性很差了。實際上對於迴圈體中的每個變數,這個函式要麼具有好的空間區域性性,要麼具有好的時間區域性性。
int sumvec(int v[N]){
int i, sum = 0;
for(i = 0; i < N; i++){
sum += v[i];
}
return sum;
}
像上面的程式碼,每隔 1 個元素進行訪問,稱之為步長
為 1 的引用模式。一般而言,隨著步長的增加,空間區域性性下降。
當然,不僅資料引用有區域性性,取指令也有區域性性。比如for
迴圈,迴圈體中的指令是按順序執行的,並且會被執行多次,所以它有良好的空間區域性性和時間區域性性。
快取記憶體
不同儲存技術的訪問時間差異很大,而我們想要的是又快又大的體驗,然而這又是違背機械原理的。為了讓程式執行的更快,計算機設計者在不同層級之間加了快取,比如在 CPU 與記憶體之間加了快取記憶體,而記憶體又作為磁碟的快取,本地磁碟又是 Web 伺服器的快取。多次訪問一個網頁,會發現有一些網路請求的狀態碼是 300,這就是從本地快取讀取的。
如下圖所示,快取記憶體通常被組織為下面的形式,計算機需要從具體的地址去拿指令或者資料,而這個地址也被切分為不同的部分,可以直接對映到快取上去。看下面詳細的介紹應該更容易理解。
直接對映快取記憶體每個組只有一行。快取記憶體確定一個請求是否命中,然後抽取出被請求的字的過程分為:組選擇
、行匹配
、字抽取
三步。
比如當 CPU 執行一條讀記憶體字w
的指令,首先從w
地址中間抽取出s
個組索引位,對映到對應的組,然後透過t
位標記確定是否有字w
的一個副本儲存在該組中;最後使用b
位的塊偏移確定所需要的字塊是從哪裡開始的。
上面這個圖,還有下面這個表,對應著看,由於能力有限,感覺怎麼都講不好,多盯著一會兒,應該就會獲得一種豁然開朗之感。
直接對映快取記憶體造成衝突不命中的原因在於每個組只有一行,組相聯快取記憶體放鬆了這一限制,每個組都儲存多於一行的快取記憶體行,所以在組選擇完成之後,需要遍歷對應組中的行進行行匹配。
當然,我們可以把每個組中的快取行數繼續擴大,即全相聯快取記憶體,所有的快取行都在一個組,它總共只有一個組。因此對地址的劃分就不需要組索引了,如下圖所示。
編寫快取友好的程式碼
float dotprod(float x[8], float y[8]){
float sum = 0.0;
int i;
for(i = 0; i < 8; i++){
sum += x[i] * y[i];
}
return sum;
}
這段函式很簡介,就是計算兩個向量點積的函式,而且對於x
和y
來說,這個函式具有很好的空間區域性性,如果使用直接對映快取記憶體,那它的快取命中率並不高。
從表中就能看到,每次對x
和y
的引用都會導致衝突不命中,因為我們在x
和y
的塊之間抖動
,即快取記憶體反覆的載入替換相同的快取記憶體塊組。
我們只需要做一個小小的改動,就能讓命中率大大提高,即讓程式執行的更快。這個改動就是把float x[8]
改為floatx[12]
,改動後的索引對映就變成下面那樣了,非常的友好。
再來看一個多維陣列,函式的功能是對所有元素求和,兩種不同的寫法。
// 第一種
int sumarrayrows(int a[M][N]){
int i, j, sum = 0;
for(i = 0; i < M; i++){
for(j = 0; j < N; j++){
sum += a[i][j];
}
}
return sum;
}
// 第二種
int sumarrayrows(int a[M][N]){
int i, j, sum = 0;
for(j = 0; j < M; j++){
for(i = 0; i < N; i++){
sum += a[i][j];
}
}
return sum;
}
從程式語言角度來看,兩種寫法的效果是一樣的, 都是求陣列所有元素的和,但是深入分析就會發現,第一種寫法會比第二種執行的更快,因為第二種寫法一次快取命中都不會發生,而第一種寫法會有 24 次快取命中,所以第一比第二種執行更快是必然的結果,第一種和第二種的快取命中模式分別如下所示(粗體表示不命中)。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555494/viewspace-2284404/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 六、層次結構儲存系統
- 層次結構資料的資料庫儲存和使用資料庫
- dump index 的層次結構Index
- 儲存結構
- Tomcat伺服器層次結構研究Tomcat伺服器
- 圖的儲存結構
- 儲存器層次結構 --《深入理解計算機系統》第六章讀書筆記計算機筆記
- Redis儲存結構以及儲存格式Redis
- JanusGraph -- 儲存結構
- CentOS 儲存結構CentOS
- TiDB 底層儲存結構 LSM 樹原理介紹TiDB
- WordPress模板層次02:模板層次結構和原理
- php圖的儲存結構PHP
- MySQL InnoDB的儲存結構總結MySql
- 繪製層次結構圖
- LB 負載均衡的層次結構負載
- Redis(一):基本資料型別與底層儲存結構Redis資料型別
- IBM AIX儲存層結構分析+aix常用命令IBMAI
- MySQL的varchar儲存原理:InnoDB記錄儲存結構MySql
- 如何構建通用儲存中間層
- 三種儲存結構
- MySQL Innodb 儲存結構 & 儲存Null值 解析MySqlNull
- 圖(Graph)——圖的儲存結構
- 串的順序儲存結構
- 【資料結構——圖和圖的儲存結構】資料結構
- MySQLInnoDB儲存引擎(一):精談innodb的儲存結構MySql儲存引擎
- MFC9.0層次結構圖
- 【PHP資料結構】圖的概念和儲存結構PHP資料結構
- InnoDB記錄儲存結構
- HBase 資料儲存結構
- redis 儲存結構原理 2Redis
- oracle物理儲存結構理解Oracle
- Oracle資料儲存結構Oracle
- SAP儲存地點結構
- 二叉樹的儲存結構二叉樹
- 佇列的順序儲存結構佇列
- 佇列的鏈式儲存結構佇列
- 嵌入式中常見的儲存器總結(一)儲存器分類